Onset: version control where nobody waits and nothing gets lost. The on command, its core and onset-server.
| 1 | # vcs spec (draft 2, October 27, 2026) |
| 2 | |
| 3 | `vcs` is a placeholder name. The real name comes before the public preview. |
| 4 | |
| 5 | vcs is open-source version control where nobody waits and nothing gets lost. It has its own model and its own CLI. Git is a bridge in and out, not the foundation. It runs on its own for one person offline, and it gets a server (Upstream, or one you host) when work is shared. |
| 6 |
| 7 | Three promises: |
| 8 | - **Nobody waits.** Landing is the server's job, so nobody's work is refused because someone else landed first. Making a workspace takes seconds, not a clone and a reinstall. |
| 9 | - **Nothing is lost.** Your work saves itself, and `vcs undo` takes back what you did, including a land. A change follows you to another machine, and deleting a workspace folder loses nothing. |
| 10 | - **Private where you need it.** A folder can be visible to only some people. Your `.env` files are yours alone, and secrets are checked before anything lands. |
| 11 |
| 12 | The plan behind this spec, with the research and the order of work, is `reports/Upstream version control plan Oct 2026.md`. |
| 13 |
| 14 | ## 1. The words |
| 15 |
| 16 | | Word | Meaning | |
| 17 | |---|---| |
| 18 | | **change** | One piece of work. It has an ID made of letters (`rtnbkcvx`; the first 4 or more letters are enough) and keeps that ID from start to finish. It holds every save. It replaces git's commit, branch, stash and reflog. | |
| 19 | | **main** | The shared line of a project. It only moves when a change lands, and each landing makes a new numbered **version**: main 1, 2, 3 and so on. Numbers are always versions and letters are always changes. | |
| 20 | | **land** | Putting a change on main. The server merges it with whatever landed since the change started, runs the project's checks on the exact result, and adds it as the next version. If something conflicts, main doesn't move and you're told exactly where. | |
| 21 | | **workspace** | A folder working on one change. You can have as many as you like, and each holds one change at a time. Your work lives in the change, not the folder. | |
| 22 | | **save** | A snapshot of a change. Saves happen by themselves, and every save is kept. | |
| 23 |
| 24 | There's no staging, no commit command, no branch names and no pull or rebase before landing. |
| 25 |
| 26 | ## 2. A day with vcs |
| 27 |
| 28 | ``` |
| 29 | $ vcs get upstream.codes/acme/shop |
| 30 | workspace ~/shop (upstream.codes/acme/shop) |
| 31 | change rtnbkcvx new, empty, on main 48190 |
| 32 | next: edit files, then vcs land -m "what you did" |
| 33 |
| 34 | $ # ...edit src/auth/login.ts... |
| 35 |
| 36 | $ vcs land -m "Fix login redirect loop" -m "Why: users with expired sessions bounced between /login and /home. Checked: auth tests pass." |
| 37 | landed rtnbkcvx as main 48214 "Fix login redirect loop" |
| 38 | merged with 23 versions that landed since main 48190 |
| 39 | change qwhmdzst new, empty, on top of what you just landed |
| 40 | next: keep working, or vcs sync to bring in others' work |
| 41 | ``` |
| 42 |
| 43 | Two agents work in parallel by each getting their own workspace: |
| 44 |
| 45 | ``` |
| 46 | $ vcs get upstream.codes/acme/shop ~/shop-billing |
| 47 | $ vcs get upstream.codes/acme/shop ~/shop-search |
| 48 | ``` |
| 49 |
| 50 | Both land whenever they're ready, in any order. Neither waits for the other or pulls first. |
| 51 |
| 52 | ## 3. Commands |
| 53 |
| 54 | Four commands cover nearly all work: **get**, **status**, **land** and **sync**. **undo** covers mistakes. |
| 55 |
| 56 | | Group | Command | What it does | |
| 57 | |---|---|---| |
| 58 | | Start | `vcs get <project> [folder]` | Make a workspace for a project. | |
| 59 | | | `vcs workspaces [add <folder> [--on <change>] \| clean]` | This project's workspaces on this machine (one per agent): list them, add one next to this one (on main, or on a change that hasn't landed), or remove the ones whose work is done. | |
| 60 | | | `vcs init [folder]` | Start a new project that lives only on this machine. | |
| 61 | | | `vcs import <git-repo> [folder]` | Make a project from a git repository, with its history. | |
| 62 | | | `vcs publish <project>` | Put a local project on a host. | |
| 63 | | | `vcs git [resume]` | Where git clones the project, and whether that git view is current. | |
| 64 | | | `vcs login [host]` | Sign in (default host: upstream.codes). | |
| 65 | | See | `vcs status [change]` | Where you are and what to do next. | |
| 66 | | | `vcs diff [path...]` | What a change changes. | |
| 67 | | | `vcs log [version\|change\|path]` | History of main, open changes, saves or operations, or the versions that changed a path. | |
| 68 | | | `vcs blame <file>` | The version that last changed each line. | |
| 69 | | | `vcs show <file> [--from <version\|change>]` | Print a file as it is in main, or in a version, change or save. | |
| 70 | | | `vcs tag [name] [version]` | Name a version of main (`v1.0`); a name works wherever a version number does. `--remove` takes it off. | |
| 71 | | Work | `vcs new ["title"]` | Start another change. The current one is kept. | |
| 72 | | | `vcs describe "text"` | Set the current change's description. | |
| 73 | | | `vcs switch <change>` | Put this workspace on another change. | |
| 74 | | | `vcs drop [change]` | Abandon a change. | |
| 75 | | Finish | `vcs sync` | Bring the change up to date with main. | |
| 76 | | | `vcs land [change]` | Put a change on main. | |
| 77 | | Fix | `vcs undo [version]` | Undo the last vcs command in this workspace, or take a landed version back out. | |
| 78 | | | `vcs restore <path...>` | Bring files back from an earlier point. | |
| 79 | | Share | `vcs access <change\|path> [who...]` | See or set who can see a change or a folder. | |
| 80 | | | `vcs group <name> [@person...]` | See or set one of the project's groups. | |
| 81 | | | `vcs approve <change>` | Approve someone else's change; the last approval a project needs lands it. | |
| 82 | | | `vcs settings [approvals=N] [git=on\|off]` | See or set the project's host settings: approvals needed to land, and whether main is copied to git. | |
| 83 | | | `vcs check` | Check this machine's objects and the host's copy of the project for damage. | |
| 84 | | | `vcs rollback --to <time>` / `--undo <bookmark>` | Put a hosted project back as it was (owners, the last 30 days), or reverse that. | |
| 85 | | Help | `vcs guide` | Print the guide for coding agents ([GUIDE.md](GUIDE.md)). | |
| 86 | | | `vcs help [command]` | Help for a command. | |
| 87 |
| 88 | Every command saves the workspace before it does anything else. Every command takes `--json`. Every command that leaves something to do ends with a `next:` line. |
| 89 |
| 90 | ### References |
| 91 |
| 92 | - `48213` is version 48213 of main, and `main` is the latest version. |
| 93 | - `rtnbkcvx` (or a unique prefix of 4 or more letters, like `rtnb`) is a change at its latest save. |
| 94 | - `rtnb:14` is save 14 of change `rtnb`. |
| 95 | - `--at "10 min ago"`, `--at 15:30` or `--at 2026-10-27T15:30` is a point in time in this workspace's saves. |
| 96 |
| 97 | ### vcs get |
| 98 |
| 99 | ``` |
| 100 | vcs get <project> [folder] [--change <id>] [--only <path>...] |
| 101 | ``` |
| 102 |
| 103 | Makes a workspace. `<project>` is `host/owner/name` (`upstream.codes/acme/shop`) or the path of a local project. The folder defaults to the project's name. |
| 104 | - The workspace starts on a new, empty change on the latest main. |
| 105 | - `--change <id>` starts on an existing change instead. If another workspace holds that change, this one takes it over (see section 7). |
| 106 | - `--only src/web docs` gets just those folders, for big projects. |
| 107 | - History is fetched when `log`, `diff` or `restore` need it. |
| 108 | - Files come from a cache shared by all your workspaces. Folders listed under `copy` in `vcs.toml` (like `node_modules`) are copied from your most recent workspace of the same project, copy-on-write where the disk supports it. |
| 109 | - Your private files (section 6) come along. |
| 110 |
| 111 | ### vcs init, import, publish, login |
| 112 |
| 113 | - `vcs init [folder]` makes a local project with no server. Landing works locally, and main is numbered locally. |
| 114 | - `vcs import <git-repo> [folder] [--to <project>]` reads a git repository (a folder, a URL, or `upstream.codes/owner/name`) with the git on your machine. Each commit on the default branch's first-parent line becomes a version, in order, in a local project in a folder named after the repository. `--to upstream.codes/acme/shop` then publishes it. `vcs import <git-repo> --change <branch>`, run in a workspace, brings one git branch in as a change instead. Section 9 covers the rest. |
| 115 | - `vcs publish <project>` puts a local project on a host. History and version numbers are kept, and from then on the host orders main. |
| 116 | - `vcs login [host]` opens the host's sign-in page in the browser and waits while you approve it (upstream.codes uses Upstream's sign-in). Hosts without one take a token: `vcs login <host> --token <token>`. Agents and CI set `VCS_TOKEN` instead. |
| 117 |
| 118 | ### vcs status |
| 119 |
| 120 | ``` |
| 121 | $ vcs status |
| 122 | project upstream.codes/acme/shop |
| 123 | workspace ~/shop |
| 124 | change rtnbkcvx "Fix login redirect loop" open, yours |
| 125 | based on main 48190 (main is at 48213; land merges the difference) |
| 126 | saved 3s ago (save 14) |
| 127 | files M src/auth/login.ts |
| 128 | A src/auth/redirect.test.ts |
| 129 | next: vcs land |
| 130 | ``` |
| 131 |
| 132 | `vcs status <change>` shows any change you can see, including its landing state. |
| 133 |
| 134 | ### vcs diff |
| 135 |
| 136 | ``` |
| 137 | vcs diff [path...] [--change <id>] [--from <ref>] [--to <ref>] [--stat] |
| 138 | ``` |
| 139 |
| 140 | By default, it shows what the current change changes compared with its base. `--from`/`--to` compare any two references (`vcs diff --from 48100 --to main`). The output is unified diff, like git's. |
| 141 |
| 142 | ### vcs log |
| 143 |
| 144 | ``` |
| 145 | vcs log [-n 20] recent versions of main |
| 146 | vcs log 48213 one version: description, author, files |
| 147 | vcs log rtnb one change: state, saves, description |
| 148 | vcs log --changes open changes you can see: who, state, which workspace |
| 149 | vcs log --saves saves of the current change |
| 150 | vcs log --ops operations in this workspace (what undo walks back through) |
| 151 | vcs log src/auth the versions that changed a file or folder (added, modified, removed) |
| 152 | vcs blame src/auth/login.ts each line with the version that last changed it |
| 153 | ``` |
| 154 |
| 155 | A path is told apart from a change by having a `/` or `.`, by not looking like a change ID, or by being a file or folder in the workspace. Hosts work out a path's history where the versions are, so a machine with only recent history still sees all of it. Each version keeps the paths it changed, so a path's history reads only the versions that touched it: on 20,000 versions, `vcs log <file>` takes about 10 ms and `vcs blame` 40 ms (git: 20–90 ms and 210 ms). Private paths have no history or blame for people who can't see them. |
| 156 |
| 157 | ### vcs new, describe, switch, drop |
| 158 |
| 159 | - `vcs new ["title"]` saves and keeps the current change, then starts a new change on the latest main. The workspace's files become latest main. |
| 160 | - `vcs new ["title"] --on <change>` starts the new change on top of another change that hasn't landed yet (stacking). Landing the top change lands the whole stack (section 4). |
| 161 | - `vcs describe "Title" ["Body"...]` sets the current change's description. The first line is the title. |
| 162 | - `vcs switch <change>` saves and keeps the current change, then puts the workspace's files on another change. If another workspace holds that change, this one takes it over. |
| 163 | - `vcs drop [change]` abandons a change (the current one by default). A dropped current change is replaced by a new empty change on the latest main. `vcs undo` brings it back. |
| 164 |
| 165 | ### vcs sync |
| 166 |
| 167 | Brings the current change up to date with the latest main, along with any unlanded changes under it. The change's base becomes the latest main. Conflicts are written into the files with git-style markers, and you can keep working while they're there. |
| 168 |
| 169 | ``` |
| 170 | $ vcs sync |
| 171 | synced rtnbkcvx onto main 48213 (was main 48190) |
| 172 | conflict src/auth/login.ts lines 40-58 |
| 173 | next: fix the marked lines in src/auth/login.ts, then vcs land |
| 174 | ``` |
| 175 |
| 176 | Markers look like this: |
| 177 |
| 178 | ``` |
| 179 | <<<<<<< main 48213 |
| 180 | return redirect(session.next ?? "/home") |
| 181 | ======= |
| 182 | return redirect(safeNext(session.next)) |
| 183 | >>>>>>> your change rtnbkcvx |
| 184 | ``` |
| 185 |
| 186 | A conflict counts as fixed once the markers are gone from that spot. For binary files, your version stays in place, and `vcs restore <file> --from main` takes main's instead. Sync exits with 3 when it leaves conflicts, and `vcs land` refuses (exit 3) while any are left. |
| 187 |
| 188 | You never need to sync before landing. Sync when you want others' work in your files, or after a land reports a conflict. |
| 189 |
| 190 | ### vcs land |
| 191 |
| 192 | ``` |
| 193 | vcs land [change] [-m "Title"] [-m "Body"]... [--allow-secrets] |
| 194 | ``` |
| 195 |
| 196 | Saves, then asks the server to land the change (the current one by default) and waits for the result. It needs a description, from `-m`, `vcs describe`, or a title given to `vcs new`. `-m` sets the change's description, so after a failed land a plain `vcs land` reuses it. Section 4 has the details. Outcomes: |
| 197 |
| 198 | | Outcome | Exit | What happened | Next | |
| 199 | |---|---|---|---| |
| 200 | | landed | 0 | The change is main N. The workspace moves to a new, empty change on top of it, and your files don't change. | Keep working. | |
| 201 | | conflict | 3 | Main didn't move, and your change is unchanged and saved. The conflicting files are listed. | `vcs sync`, fix, `vcs land` | |
| 202 | | checks failed | 4 | Main didn't move. A `[land] run` command failed (its output is printed), or a host check did (a link to its log is printed; required checks come later, see section 14). | Fix, then `vcs land` | |
| 203 | | waiting | 5 | You can't land this on your own: your token can't land, or you're a reader or an outside contributor. The people who can land it are asked. The workspace moves to a new change on top of it, so later edits don't change what's waiting. | `vcs status <change>` later | |
| 204 | | secret | 8 | Something in the change looks like a secret. The file and line are printed. | Remove it, or move it to a private file | |
| 205 | | offline | 6 | The host can't be reached. The change is saved. | `vcs land` again later | |
| 206 |
| 207 | Landing someone else's change (`vcs land rtnb`) is how a person with landing rights accepts an agent's change, a teammate's change or an outside contributor's change. |
| 208 |
| 209 | ### vcs undo |
| 210 |
| 211 | Undoes the last vcs command in this workspace. Run it again to go further back, and `vcs log --ops` lists what it will walk through. |
| 212 |
| 213 | | Undoing | Does | |
| 214 | |---|---| |
| 215 | | land | Adds a new version to main that takes the landed change back out, then reopens the change in this workspace. Edits made since the land stay in the files and become part of the reopened change. If later versions changed the same lines, undo stops and says so. | |
| 216 | | sync | Puts the change and the files back as they were before the sync. | |
| 217 | | new, switch | Goes back to the previous change and its files. | |
| 218 | | drop | Brings the change back. | |
| 219 | | describe, access | Puts the old description or rule back. | |
| 220 | | restore | Puts the files back as they were before the restore. | |
| 221 |
| 222 | `get`, `init`, `import`, `publish` and `login` aren't undone. Delete the folder, or use the host. |
| 223 |
| 224 | `vcs undo <version>` takes an earlier landed version back out without undoing anything else. It adds a new version to main that reverses it, and the change it came from becomes open again (`vcs switch <change>` to work on it). It stops and says so if later versions changed the same lines. |
| 225 |
| 226 | ### vcs restore |
| 227 |
| 228 | ``` |
| 229 | vcs restore <path>... back to how it is in the change's base |
| 230 | vcs restore <path>... --at "10 min ago" from your own earlier save |
| 231 | vcs restore <path>... --from <ref> from a version, a change or a save |
| 232 | ``` |
| 233 |
| 234 | `vcs restore .` restores everything. Restoring is itself saved, so it can be undone. |
| 235 |
| 236 | ### vcs access |
| 237 |
| 238 | ``` |
| 239 | vcs access <change|path> show who can see it |
| 240 | vcs access <change|path> <who>... set who can see it |
| 241 | vcs access <change> <who>... --edit also let them take the change over and work on it |
| 242 | ``` |
| 243 |
| 244 | `<who>` is `me`, `@person`, `@group` (one of the project's groups, set with `vcs group`), `team` (everyone with access to the project) or `everyone` (public projects only). Section 6 has the rules. |
| 245 |
| 246 | ## 4. Landing |
| 247 |
| 248 | 1. **Saved first.** `vcs land` saves, then sends the change's final save. Edits made after that point go into the next change. |
| 249 | 2. **Ordered by the server.** Each project's main has one queue, and landings are decided one at a time in the order they arrive. A land is never refused because someone else landed first. |
| 250 | 3. **Merged on the server.** The server does a 3-way merge of each file: the change's base, the latest main and the change. |
| 251 | - Text files merge line by line, as in git. |
| 252 | - JSON files merge by key, so two changes that each add keys to the same file both land. |
| 253 | - Anything that can't be merged is a conflict, and the land stops with the list of files and lines. |
| 254 | 4. **It lands at once.** Required checks on the merged result, before main moves, come later (section 14). |
| 255 | 5. **One change, one version.** The version's description is the change's description, and its author is the change's author (a person, or an agent acting for one). |
| 256 | 6. **Stacks land in order.** Landing a change that sits on unlanded changes lands all of them, bottom first, as consecutive versions. If one conflicts, the ones below it stay landed, and it and the ones above it don't. The error says which. |
| 257 | 7. **After landing,** the workspace continues on a new, empty change on top of what landed. Your files don't change. vcs never changes files in a workspace unless you run a command that says it does: sync, new, switch, restore or undo. |
| 258 | 8. **Who can land comes from the host.** On Upstream, a repository's admins and writers land; anyone else's land waits, and the people who can land are asked. A token only does what its scopes allow, so a read-only token's land waits too. |
| 259 | - **Approvals:** with `vcs settings approvals=1` (or more), a land waits until that many people other than the change's owner run `vcs approve <change>`. An approval counts for exactly what was approved, so editing the change needs a new one. The last approval lands the change. |
| 260 | 9. **Outside contributors** don't need a fork. They get the project, make a change, and `vcs land` asks the maintainers, who land it or don't. |
| 261 |
| 262 | ## 5. Saving, undo and restore |
| 263 |
| 264 | - **Every vcs command saves first.** Between commands, a background **saver** saves any workspace within a few seconds of a file changing. |
| 265 | - The saver is one process per machine. It starts with the first vcs command and stops after 30 minutes with no activity. |
| 266 | - `VCS_SAVER=0` turns it off. |
| 267 | - **Saves stay local first,** then upload to the host in the background. On the host they're visible only to you and your agents, unless you share the change. |
| 268 | - **Saves include every file that isn't ignored.** Ignored files (from `.gitignore`, same syntax) are never saved and never land. |
| 269 | - **Kept:** every save and every landed version. (Cleaning up old saves of landed and dropped changes comes later.) |
| 270 | - **`vcs undo`** walks back through operations. **`vcs restore`** walks back through file contents. Between them, the only way to lose work is to delete a folder before its last edits were saved, which takes under a few seconds with the saver running. Running any vcs command (`vcs status`) before deleting a folder makes sure. |
| 271 | - **`vcs check`** reads every object this machine keeps and checks it against its ID, and asks the host to check its copy: every version's ID against its contents and the version before it, and that everything main needs is stored. It exits 1 if anything is damaged. (21,178 versions and main's files: 0.6 s on Upstream's host.) |
| 272 | - **`vcs rollback --to <time>`** (owners) puts the whole hosted project back as it was at a time in the last 30 days: versions, changes, saves and settings. It prints a bookmark, and `vcs rollback --undo <bookmark>` puts it back as it was before. Workspaces follow on their next `vcs sync`, keeping their own unlanded edits. A project with a git view pauses it, since git main still has what came after; `vcs git resume` keeps those commits in git's history (as a second parent) while main's files are the rolled-back ones. |
| 273 | - **Nothing can write into `.git` or `.vcs`.** A path part can't be either in any spelling a disk treats as the same (`.GIT`, `.git.`, `git~1`), nor hold a backslash: hosts refuse to land one, imports leave them out, and vcs never writes one into a folder or picks up a nested `.git` or `.vcs` folder. On Windows, vcs refuses to write names Windows can't hold (`aux.c`, `a:b`) rather than write somewhere else. |
| 274 |
| 275 | ## 6. Access |
| 276 |
| 277 | Who can see what: |
| 278 |
| 279 | | Thing | Default | Changed with | |
| 280 | |---|---|---| |
| 281 | | An open change | You, and agents acting for you. The maintainers too, once `land` has asked them. | `vcs access <change> <who>` | |
| 282 | | A path in the project | Everyone with access to the project | `vcs access <path> <who>` (project owners only) | |
| 283 | | `.env` and `.env.*`, except `.env.example`, `.env.sample` and `.env.template` | **Mine**: only you | `vcs.toml` `[mine]` or `vcs access <path> me` | |
| 284 |
| 285 | - **A private path** (for example `vcs access billing/ @sam`) is left out of the workspaces, diffs, logs and API answers of anyone it isn't shared with, and out of the git view for everyone (section 9). Project owners see every path. |
| 286 | - It lands normally, and main's version numbers are the same for everyone. |
| 287 | - A version that changed only paths you can't see shows as "a private change", without its description. |
| 288 | - Writing into a path you can't see is refused when you land. |
| 289 | - Agents see what the person they act for sees. |
| 290 | - **Mine files** are saved and follow you to every workspace of the project you make, so a new workspace has your `.env` already. They never land, never show to anyone else and never appear in git views. On a host, only you can read them. |
| 291 | - **Secret check:** before landing, added lines are checked for things that look like real keys and tokens. A match stops the land (exit 8) with the file and line. `--allow-secrets` overrides it, and the override is recorded. Saves are private to you, but once you remove a flagged secret, it's also scrubbed from that change's earlier saves. This is the one case where saves are rewritten. |
| 292 | - **Path rules** are project settings, not files in the project, so a private folder's name isn't exposed to people who can't see it. |
| 293 |
| 294 | ## 7. Workspaces |
| 295 |
| 296 | - A workspace is a normal folder with a small `.vcs/` folder inside. There is no `.git`. Git commands don't work in a workspace; section 9 covers git. |
| 297 | - Making a second workspace of a project takes seconds. Files come from a shared cache, copy-on-write where the disk supports it (APFS clones on macOS, reflinks on Btrfs and XFS). |
| 298 | - `vcs.toml` can list build folders to copy from your most recent workspace of the same project: |
| 299 |
| 300 | ```toml |
| 301 | [workspace] |
| 302 | copy = ["node_modules", "target"] |
| 303 | ``` |
| 304 |
| 305 | - **One change per workspace, one workspace per change.** If another workspace (on this machine or another) holds a change and you `switch` to it or `get --change` it, you take it over. The other workspace finds out on its next command. If it had edits that weren't saved yet, they're saved into a new change labelled "rescued from rtnbkcvx", so nothing is lost. |
| 306 | - **Deleting a workspace** is just deleting the folder. Its change stays, and `vcs log --changes` lists it, ready for `vcs get --change`. |
| 307 | - **One workspace per agent.** Two processes editing one folder at once are the classic way to lose work silently. vcs keeps every save, but it can't tell two writers apart. |
| 308 |
| 309 | ## 8. Offline and local projects |
| 310 |
| 311 | - **Offline,** everything works except land, get, access and the parts of log and status that need the host's latest main. Sync uses the last main this machine saw. Saves queue and upload once you're back online. |
| 312 | - **A local project** (`vcs init`) has no host at all, and landing is local. `vcs publish` moves it to a host whenever you want, with history and version numbers kept. |
| 313 | - **Full copies:** anyone who can read a project can read every version of it, through vcs or its git view, so a public project can always be copied or mirrored. |
| 314 |
| 315 | ## 9. Git and GitHub |
| 316 |
| 317 | vcs is native, but git still works around it, so nobody has to switch at once. |
| 318 |
| 319 | - **Import:** `vcs import <git-repo>` reads a repository with the git on your machine. |
| 320 | - Each commit on the default branch's first-parent line becomes a version, in order, with its message, author and time. |
| 321 | - Each git file and folder is read once, however many commits share it. |
| 322 | - Submodules come along as pointers to their commits: a workspace shows each as an empty folder (as git does before `git submodule update`), and the git view keeps them. Paths whose names aren't UTF-8 are left out (the import says how many). |
| 323 | - **Publishing an import** (`vcs import <repo> --to <project>`, or a later `vcs publish`) also copies the original git history, every branch and tag byte for byte, to the project's git view, and main there is the last imported commit. A repository that already has that history (importing a project from its own Upstream repository) is left as it is. |
| 324 | - **The git view:** every hosted project on Upstream is also its Upstream repository, so git clones it as usual (`git clone https://git.coline.app/acme/shop.git`), and Upstream's GitHub mirror keeps a GitHub copy current. |
| 325 | - Main is a git branch, and each version lands there as a commit within seconds. Its message ends with `Vcs-Change: rtnbkcvx` and `Vcs-Version: 48214`. A busy project's versions go in at most one push a second, many versions to a push. |
| 326 | - The git view is one view for everyone who can read the repository, so it leaves out every private path. |
| 327 | - `vcs git` shows where it clones from and which commit is the latest version. `vcs settings git=off` stops copying main to git (for a team that's left git behind); `git=on` catches it up. |
| 328 | - **Git branches come in as changes:** `vcs import <git-repo> --change <branch>`, run in a workspace, makes the branch a change on top of the version it started from, ready for `vcs land`. It works on any branch of the git view, and on git commits pushed to main. |
| 329 | - **Pushing to git main works too.** On Upstream, commits pushed to git main come into vcs within seconds of the next request (Upstream says when a repository gets a push): each commit on git main's first-parent line becomes a version with its message and author, already in git. If vcs landed meanwhile, git main's commits merge into one version and the git view's next push makes it a merge commit of both lines, so neither side loses anything. Private paths aren't in git, so they stay as they were. |
| 330 | - **When they can't come in by themselves** (git and vcs changed the same lines, a commit touches a private path, a force push, or over 32 MB at once), the git view pauses instead of overwriting anything, and `vcs git` says why. Bring those commits in with `vcs import <repo> --change main` and land them, then `vcs git resume`: the next version's commit keeps the outside commits as a second parent, so git history loses nothing. |
| 331 |
| 332 | | git | vcs | |
| 333 | |---|---| |
| 334 | | `git clone` | `vcs get` | |
| 335 | | `git status` | `vcs status` | |
| 336 | | `git add` + `git commit` | nothing; it saves itself | |
| 337 | | `git commit -m` + `git push` | `vcs land -m` | |
| 338 | | `git pull`, `git rebase main` | `vcs sync` | |
| 339 | | `git checkout -b`, `git switch -c` | `vcs new` | |
| 340 | | `git switch <branch>` | `vcs switch <change>` | |
| 341 | | `git stash` | `vcs new` (the current change stays saved) | |
| 342 | | `git checkout -- <file>`, `git restore <file>` | `vcs restore <file>` | |
| 343 | | `git reflog`, `git reset --hard HEAD@{1}` | `vcs undo` | |
| 344 | | `git log` | `vcs log` | |
| 345 | | `git log -- <file>`, `git blame <file>` | `vcs log <file>`, `vcs blame <file>` | |
| 346 | | `git show <rev>:<file>` | `vcs show <file> --from <version>` | |
| 347 | | pre-commit and pre-push hooks | `[land] run` in `vcs.toml` | |
| 348 | | `git tag v1.0`, `git push --tags` | `vcs tag v1.0` (the git view gets it as a git tag) | |
| 349 | | `git worktree add` | `vcs get <project> <folder>` | |
| 350 | | `git revert <commit>` | `vcs undo <version>` (or plain `vcs undo` right after the land) | |
| 351 | | a pull request | `vcs land` without landing rights, or `vcs access <change> team` | |
| 352 | | a branch someone pushed with git | `vcs import <repo> --change <branch>` | |
| 353 |
| 354 | ## 10. Agents |
| 355 |
| 356 | - `vcs guide` prints [GUIDE.md](GUIDE.md), a short instruction file. Agent harnesses can load it as a skill or paste it into `AGENTS.md`. |
| 357 | - **Tokens:** agents use a token in `VCS_TOKEN`, made by a person on the host. The token acts for that person and can be limited further, to some projects and to reading or writing. The host enforces the limits, so instructions don't have to. |
| 358 | - **Parallel agents** each get their own workspace with `vcs get`. Second and later workspaces take seconds, and a project's `copy` folders come along. |
| 359 | - **Output:** short text with a `next:` line, or `--json` with the same fields plus `next`. Exit codes are stable (section 11). |
| 360 | - **Without a checkout:** `POST /projects/{owner}/{name}/edits` lands file edits in one call: contents (text, or base64 for binary files), deletions, which files are executable, a title and the version they were made on. It becomes a change of the caller's and lands like any other: merged, waiting for approvals or landing rights, refused for secrets unless asked. Upstream's host also speaks MCP at `https://vcs.coline.dev/mcp` (the same tokens), with tools to read a file, list a folder, see recent versions and land edits, for agents that have no shell or disk. Upstream's own API and MCP server read files, search and diff the git view. |
| 361 |
| 362 | ## 11. Output and exit codes |
| 363 |
| 364 | Text output is short and stable: one fact per line, a label in the first column, and `next:` last. Errors start with `error:` and always say whether anything changed. |
| 365 |
| 366 | ``` |
| 367 | error: can't land rtnb: 2 files conflict with main 48213 |
| 368 | src/auth/login.ts |
| 369 | assets/logo.png (binary) |
| 370 | main didn't change; your change is saved |
| 371 | next: vcs sync, fix the conflicts in those files, then vcs land |
| 372 | ``` |
| 373 |
| 374 | `--json` prints one JSON object: |
| 375 |
| 376 | ```json |
| 377 | { |
| 378 | "ok": false, |
| 379 | "error": { "code": "conflict", "message": "can't land rtnb: 2 files conflict with main 48213" }, |
| 380 | "main": 48213, |
| 381 | "conflicts": [ |
| 382 | { "path": "src/auth/login.ts", "kind": "text" }, |
| 383 | { "path": "assets/logo.png", "kind": "binary" } |
| 384 | ], |
| 385 | "next": ["vcs sync, fix the conflicts in those files, then vcs land"] |
| 386 | } |
| 387 | ``` |
| 388 |
| 389 | | Exit | `error.code` | Meaning | |
| 390 | |---|---|---| |
| 391 | | 0 | | OK | |
| 392 | | 1 | `failed`, `not_found`, `empty_change`, `nothing_to_undo` | Didn't work. The message says why. | |
| 393 | | 2 | `bad_usage`, `no_description` | The command was wrong. `next` shows the right form. | |
| 394 | | 3 | `conflict` | Conflicts. Sync and fix. | |
| 395 | | 4 | `checks_failed` | A `[land] run` command in `vcs.toml` failed, or a required check failed on the merged result. | |
| 396 | | 5 | `waiting` | Someone else has to act (land or approve). Don't retry in a loop. | |
| 397 | | 6 | `offline` | The host can't be reached. The work is saved. | |
| 398 | | 7 | `not_allowed`, `not_logged_in` | Permissions. | |
| 399 | | 8 | `secret` | Possible secret in the change. | |
| 400 |
| 401 | ## 12. vcs.toml |
| 402 |
| 403 | Optional, at the project's root, and it lands like any file. |
| 404 |
| 405 | ```toml |
| 406 | [workspace] |
| 407 | copy = ["node_modules", "target"] # build folders new workspaces copy from your latest workspace |
| 408 |
| 409 | [mine] |
| 410 | paths = [".env", ".env.*", "!.env.example", "!.env.sample", "!.env.template"] # the default |
| 411 |
| 412 | [land] |
| 413 | run = ["pnpm lint", "pnpm test"] # vcs land runs these in the workspace first; if one fails, nothing lands (vcs land --no-run skips them) |
| 414 | ``` |
| 415 |
| 416 | `[land] run` is this machine's, as git hooks are. Required checks, approvals and who can land are host settings, not file settings, so a change can't loosen its own rules. |
| 417 |
| 418 | ## 13. Limits and targets |
| 419 |
| 420 | | | Target | Measured (Oct 27, `tests/perf.sh`, self-hosted server, one Linux machine) | |
| 421 | |---|---|---| |
| 422 | | `vcs status` on 20,000 files | under 100 ms | 66 ms | |
| 423 | | Second workspace of a 15,000-file project | under 2 s | 0.6 s | |
| 424 | | Land with no required checks, server time | under 50 ms | 0.5 ms median | |
| 425 | | Landings on one main | 400 or more per second | about 2,000 per second; 63 agents landing at once all landed in 1.2 s | |
| 426 | | A save showing up on another of your machines | under 5 s | not measured yet | |
| 427 | | One file | up to 5 GB, stored in chunks, so a small edit to a big file uploads only what changed | | |
| 428 | | One project | up to 100 GiB, the same as Upstream Rapid today | enforced by Upstream's host | |
| 429 |
| 430 | Against stock git (`tests/bench.sh` on one machine, git with a bare repository on the same disk; `tests/bench-live.sh` against git.coline.app and vcs.coline.dev; Oct 28): |
| 431 |
| 432 | | | git | vcs | |
| 433 | |---|---|---| |
| 434 | | `status`, 20,000 files | 16 ms | 54 ms | |
| 435 | | One change, commit + push vs land (same machine / live) | 0.07 s / 0.81 s | 0.12 s / 0.30 s | |
| 436 | | First copy, 20,000 files (same machine) / 275 files and 300 versions (live) | 1.26 s / 1.47 s | 0.98 s / 0.49 s | |
| 437 | | 8 / 32 / 64 agents landing on one branch (same machine) | 0.32 / 3.2 / 13.3 s, 1,960 pushes refused at 64 | 0.04 / 0.14 / 0.28 s, none refused | |
| 438 | | 16 agents landing on one branch (live) | 27.1 s, 107 pushes refused | 1.45 s, none refused | |
| 439 |
| 440 | Git's first copy carries all history; vcs fetches history when it's needed. |
| 441 |
| 442 | Upstream's host (`vcs.coline.dev`), measured Oct 28 from a machine in us-west-2: |
| 443 |
| 444 | | | Measured | |
| 445 | |---|---| |
| 446 | | `vcs land`, whole command | about 0.3 s (two requests) | |
| 447 | | 64 agents landing at once on one main | all 64 landed in 0.8 s | |
| 448 | | A fresh workspace of a small project | 0.23 s | |
| 449 | | A 15,000-file project: land, status, first workspace on a new machine | 0.28 s, 0.05 s, 4.6 s | |
| 450 | | `POST /edits` (landing without a workspace) | 0.15 s | |
| 451 | | The git view catching up 173 versions | 6 s, in batches of 64 per push | |
| 452 |
| 453 | Under load (`tests/load.mjs`, agents landing edits over HTTP as fast as they can, each to its own file; Oct 28): |
| 454 |
| 455 | | | Measured | |
| 456 | |---|---| |
| 457 | | 20 projects × 25 agents, 60 s, plus 40 readers | 184,268 landed, 3,052 a second; none failed; landing p50 141 ms, p99 579 ms; reads p50 138 ms | |
| 458 | | 20 projects × 75 agents (1,500 agents), 45 s | 4,094 landed a second, about 200 a second per project; none failed | |
| 459 | | One project, 256 agents | about 100 landed a second; none failed; each waits its turn (p50 2.2 s) | |
| 460 | | One project with its git view on, 64 agents | about 70 landed a second; the git view caught up 17 s after a 20 s burst (256 versions a push, about 2 s each to git.coline.app) | |
| 461 | | A version reaching the git view when the project is quiet | about 0.35 s | |
| 462 | | Host CPU a landing (Durable Object) | about 3 ms (it was 17 ms before edits stopped rebuilding the whole tree) | |
| 463 |
| 464 | A project is one Durable Object, so projects scale out; one project's landings are one queue. |
| 465 |
| 466 | Platforms: macOS, Linux and Windows (x64 and arm64 each). On Windows a file keeps the kind vcs knows it as (executable, link) when edited, as git does there, since the disk can't always say. |
| 467 |
| 468 | ## 14. Not in this version |
| 469 |
| 470 | - Required checks before a change lands. |
| 471 | - A git view per person, with only what they can see. The one git view leaves out every private path. |
| 472 | - Changing a submodule's commit from vcs (it changes in git and comes in with `vcs import --change`), and SHA-256 git views (Git 3.0). |
| 473 | - Cleaning up old saves, and encrypting private files on the host. |
| 474 | - Lines other than main (release lines). Imported git tags stay in the git view; tags made in vcs name versions of main. |
| 475 | - One change across several projects. |
| 476 | - Embargoed changes that publish on a date. |
| 477 | - Deleting a landed file from history. |
| 478 | - A mounted filesystem that fetches files when they're read. `--only` covers big projects for now. |
| 479 | - Rename tracking. A move shows as a delete plus an add. |
| 480 | - Structure-aware merging beyond JSON. |
| 481 | - Review screens. They belong to the host (Upstream). |
| 482 | - Agent session transcripts. |
| 483 | - Editor integrations, and a git folder inside workspaces for editors' git panels. |
| 484 |
| 485 | ## 15. For implementers |
| 486 |
| 487 | - **Core** (`vcs/core`, Rust, no I/O): |
| 488 | - Objects are blobs, chunk lists and trees, each named by SHA-256 of a kind byte and its data. Files over 1 MiB are split into content-defined chunks (FastCDC, 16 KiB to 256 KiB, about 64 KiB on average). |
| 489 | - A tree is a sorted list of entries (kind, name, ID), and kinds are file, executable, link and folder. |
| 490 | - A version records its number, tree, change, author, description, time and the previous version's hash, so main is a hash chain. |
| 491 | - Merges are 3-way per file (histogram diff, git-style markers), with JSON merged by key. Private paths are filtered out of trees and grafted back when someone who can't see them lands. |
| 492 | - Bundles carry objects between machines: `VCSB1` frames, zstd when it helps, each checked against its ID on arrival. |
| 493 | - **CLI** (`vcs/cli`): one Rust binary on the core. SQLite and one file per object under `VCS_HOME` (by default `~/.local/share/vcs`), shared by every workspace. The saver watches with FSEvents on macOS and inotify on Linux, and looks at active workspaces every 30 seconds for anything the watches missed. `VCS_TRACE=1` prints each call to the host with its status and time. |
| 494 | - **Protocol:** HTTPS with JSON control calls and bundle uploads and downloads (`vcs/cli/src/wire.rs` lists every call). A host names its API in `/.well-known/vcs.json`. |
| 495 | - **Self-hosted server** (`vcs/server`, `vcs-server`): the same protocol on SQLite and a folder of objects, with its own accounts and tokens. It has no git view. |
| 496 | - **Upstream's host** (`apps/vcs`, at `vcs.coline.dev`): a Cloudflare Worker with one Durable Object per project (SQLite for versions, changes, saves, rules and the object index) and objects in R2, where each upload is stored as it came and indexed by offset. A long history leaves a tree's files spread across many uploads, so the host keeps a snapshot of a recent tree's objects in one R2 object, as git hosts keep packs ready for clones: a new machine gets that snapshot plus what changed since (on a 20,000-version project, 1.4–2 s against 18 s). The core runs in it as Wasm (`vcs/wasm`) for merges, private paths and the git view. Accounts, roles and tokens are Upstream's, from its public API. The git view is written by pushing to the repository through `git.coline.app` with the lander's token, like any git client, so Upstream Rapid is unchanged. |
| 497 | - **Licenses:** the core, CLI and protocol are Apache-2.0, and `vcs-server` is AGPL. |
| 498 |