| 1 | # grove core model |
| 2 | |
| 3 | The objects grove stores, the rules they obey, and the operations people and agents perform. |
| 4 | Read with [VISION.md](../VISION.md). Built in Rust with no dependencies beyond the standard library. |
| 5 | |
| 6 | ## Overview |
| 7 | |
| 8 | ``` |
| 9 | person ── owns ──> project ── main: point 1 → point 2 → … → point n (landed history) |
| 10 | │ │ |
| 11 | └─ agents └─ changes ── discussion (messages, events) |
| 12 | (act for │ |
| 13 | the person) └─ revisions ── steps (snapshot + message) |
| 14 | └─ evidence (anything the agent attaches) |
| 15 | ``` |
| 16 | |
| 17 | A project's main line is a numbered sequence of snapshots. Work happens in changes. A change |
| 18 | collects revisions of a stack of steps; the owner lands one revision, and its steps become the |
| 19 | next points on main. |
| 20 | |
| 21 | ## Identity |
| 22 | |
| 23 | **Person.** A human account: username, display name, passkeys for signing in. |
| 24 | |
| 25 | **Agent.** Belongs to one person and acts only for them. It has a name the person chose |
| 26 | ("claude on laptop", "operator: grove"), and its own tokens. Every write an agent makes is |
| 27 | recorded as *agent A for person P*. A person can revoke an agent, which kills its tokens and |
| 28 | leaves its history intact. |
| 29 | |
| 30 | People can also act directly, from the web. Then the actor is just the person. |
| 31 | |
| 32 | ## Projects |
| 33 | |
| 34 | A project has an owner (a person), a name unique within the owner, a description, and a |
| 35 | visibility: |
| 36 | |
| 37 | - **public:** anyone can read it. |
| 38 | - **private:** only the owner and the project's contributors can read it. |
| 39 | |
| 40 | Taking part (opening changes, revising, discussing, attaching evidence) is for the owner and |
| 41 | **contributors**: people the owner invited. An **invite** is a one-time code, created by a |
| 42 | person in person. It lets someone join the server, and if it names a project, makes them a |
| 43 | contributor to it. Nobody joins the server without one. |
| 44 | |
| 45 | Only the owner (and people they make co-owners) can land, decline, or change settings, and |
| 46 | only in person: these are decisions, so an agent acting for the owner can't make them. |
| 47 | |
| 48 | ## Content |
| 49 | |
| 50 | Content is stored by hash, so identical files are stored once and nothing can change in place. |
| 51 | |
| 52 | - **Blob.** The bytes of one file. Id: BLAKE3 of `"grove blob\0"` followed by the bytes. |
| 53 | - **Tree.** One directory: a sorted list of entries, each `name`, `kind` (`file`, `exec`, |
| 54 | `link`, `dir`) and the id of a blob or tree. A `link` is a blob holding the link target. |
| 55 | Id: BLAKE3 of `"grove tree\0"` followed by the tree's canonical encoding. |
| 56 | The prefixes keep a file and a tree with the same bytes from sharing an id. |
| 57 | - **Snapshot.** The id of a root tree: the whole project at one moment. |
| 58 | |
| 59 | Names are UTF-8 and may not contain `/` or be `.` or `..`. Binary and text files are the same |
| 60 | to storage; diffs are computed for text and shown as size changes otherwise. |
| 61 | |
| 62 | Ignored paths come from a `.groveignore` file in the project, same syntax as the usual ignore |
| 63 | files. The CLI applies it; the server stores whatever it's given. |
| 64 | |
| 65 | ## Main |
| 66 | |
| 67 | Main is the project's history: a linear sequence of **points**, numbered from 1. |
| 68 | |
| 69 | | field | | |
| 70 | |---|---| |
| 71 | | `n` | 1, 2, 3, … with no gaps | |
| 72 | | `snapshot` | the project after this point | |
| 73 | | `change`, `step` | the change and step that produced it | |
| 74 | | `message` | the step's message | |
| 75 | | `landed_by` | who landed it (person), and when | |
| 76 | | `authored_by` | agent-for-person that wrote the step | |
| 77 | |
| 78 | There are no branches and no merges on main. Work in progress lives in changes. Main only |
| 79 | ever grows; a mistake is undone by landing a change that reverts it. |
| 80 | |
| 81 | Agents and the web refer to versions as `owner/project@n`, and `@main` for the latest. |
| 82 | |
| 83 | ## Changes |
| 84 | |
| 85 | A change is something someone wants done to a project. It is numbered within the project |
| 86 | (`owner/project#12`). |
| 87 | |
| 88 | | field | | |
| 89 | |---|---| |
| 90 | | `number` | per project | |
| 91 | | `intent` | what it's for, in plain words; editable, every version kept | |
| 92 | | `proposer` | person or agent-for-person who opened it | |
| 93 | | `state` | see below | |
| 94 | | `revisions` | zero or more, in order | |
| 95 | | `discussion` | messages and events | |
| 96 | |
| 97 | ### States |
| 98 | |
| 99 | ``` |
| 100 | idea ──> draft ──> proposed ──> landed |
| 101 | │ │ │ |
| 102 | └────────┴───────────┴──> declined / withdrawn |
| 103 | ``` |
| 104 | |
| 105 | - **idea:** intent and discussion only, no revisions. |
| 106 | - **draft:** has revisions; the author is still working. Not asking for a decision. |
| 107 | - **proposed:** the author asks the owner to decide on the latest revision. |
| 108 | - **landed:** the owner landed it; its steps are on main. |
| 109 | - **declined:** the owner said no. **withdrawn:** the proposer gave up. |
| 110 | |
| 111 | Declined and withdrawn changes can be reopened. Nothing is ever deleted. |
| 112 | |
| 113 | ### Revisions and steps |
| 114 | |
| 115 | A **revision** is one complete version of the work, immutable once created: |
| 116 | |
| 117 | | field | | |
| 118 | |---|---| |
| 119 | | `number` | r1, r2, … within the change | |
| 120 | | `base` | the main point it was built on | |
| 121 | | `steps` | ordered list; each step builds on the one before | |
| 122 | | `note` | what changed since the previous revision, optional | |
| 123 | | `created_by` | agent-for-person | |
| 124 | |
| 125 | A **step** within a revision is a snapshot plus a message. Each step has a stable id inside |
| 126 | the change, so the same step can be followed across revisions ("step 2 in r1, r2 and r4"), |
| 127 | even as it's edited, reordered, split or dropped. Revising one step means creating a new |
| 128 | revision; later steps carry forward, rebuilt on top of it if needed. |
| 129 | |
| 130 | This keeps Sapling's useful property, that amending is free and nothing is lost, without |
| 131 | tracking each amend as its own object. The diff between two revisions (or between two |
| 132 | versions of one step) shows exactly what the agent did in response to feedback. |
| 133 | |
| 134 | ### Evidence |
| 135 | |
| 136 | Evidence is attached to a revision. The agent decides what to attach. |
| 137 | |
| 138 | | field | | |
| 139 | |---|---| |
| 140 | | `title` | short label | |
| 141 | | `type` | a media type (`text/markdown`, `image/png`, `application/json`, `text/uri-list`, …) | |
| 142 | | `content` | a blob, or a URL for something running elsewhere (a preview) | |
| 143 | | `step` | optional: the step it's about | |
| 144 | | `created_by` | agent-for-person | |
| 145 | |
| 146 | grove stores evidence as given and renders what it knows how to: markdown, images, video, |
| 147 | logs, tables, links to previews. Anything else downloads. |
| 148 | |
| 149 | ### Discussion |
| 150 | |
| 151 | A change's discussion is one timeline of **messages** and **events**. |
| 152 | |
| 153 | A message has an author (person, or agent-for-person), a markdown body, optional attachments |
| 154 | (stored like evidence), an optional reply-to, and an optional anchor: a revision, step, file |
| 155 | or line it's about. Messages can be edited; every version is kept. |
| 156 | |
| 157 | Events are written by grove: state changes, new revisions, evidence added, landing results. |
| 158 | |
| 159 | Steering is a message. The owner (or their agent) writes in the discussion; the building |
| 160 | agent reads it and responds with a new revision, a reply, or both. grove doesn't interpret |
| 161 | the request. |
| 162 | |
| 163 | ## Landing |
| 164 | |
| 165 | grove never merges. A change lands only on the point it was built on. |
| 166 | |
| 167 | 1. When the owner lands revision R of change C, and `R.base` is the tip of main, each step |
| 168 | becomes the next point, in order, in one record: all of the change lands or none of it. |
| 169 | 2. If main has moved since R was built, landing is refused. The building agent moves its |
| 170 | working directory to the tip (`restart`), redoes the work there, and proposes a new |
| 171 | revision. Its earlier revisions stay readable (`diff #C r1`) to redo from. |
| 172 | |
| 173 | Agents rewrite files; they don't need a merge algorithm to do it, and nothing in grove's |
| 174 | history is ever a mix nobody wrote. |
| 175 | |
| 176 | ## Agents' workflow |
| 177 | |
| 178 | From an agent's side, through the CLI or the API: |
| 179 | |
| 180 | 1. `checkout owner/project`: files at `@main` in a working directory, with `.grove/` |
| 181 | holding local state. |
| 182 | 2. Edit files. `step "message"` records the working directory as a new step; `amend` replaces |
| 183 | the current step; `stack` shows the local steps. |
| 184 | 3. `propose` uploads the local steps as a change, or as a new revision of the existing one, |
| 185 | sending only blobs the server doesn't have. |
| 186 | 4. `attach` adds evidence. `say` posts to the discussion. `watch` checks the change every few |
| 187 | seconds and prints what's new in its discussion, so the agent can respond to steering. |
| 188 | 5. `sync` brings the working directory up to the new tip. With local steps on an older point, |
| 189 | `restart` moves to the tip and the agent redoes the work as a new revision. |
| 190 | |
| 191 | |
| 192 | ## Storage |
| 193 | |
| 194 | grove has no database. Two kinds of files hold everything: |
| 195 | |
| 196 | - **Objects** (blobs and trees) live under `objects/<first 2 hex>/<rest of id>`. An object is |
| 197 | written to a temporary file, flushed to disk, renamed into place, and the directory flushed, |
| 198 | before any record refers to it. Objects are never rewritten. |
| 199 | - **The log** holds every other fact as an append-only sequence of records: person and agent |
| 200 | created, project created, change opened, revision added, message posted, state changed, |
| 201 | point landed. Each record is `length, checksum, payload` (payload is JSON; checksum is the |
| 202 | first 8 bytes of its BLAKE3). A write is acknowledged only after the record is flushed to |
| 203 | disk. On start, grove replays the log into memory and answers every query from memory. |
| 204 | - **Torn writes.** If the process dies mid-append, the last record fails its length or |
| 205 | checksum. On start, grove cuts the file back to the last whole record. Damage anywhere |
| 206 | but the tail stops grove with an error instead of guessing. |
| 207 | - **One writer.** The store takes an exclusive file lock while open. |
| 208 | - **Backups** are a copy of the log and the objects directory, both append-only, so backups |
| 209 | are incremental. An integrity check confirms every object a record refers to exists and |
| 210 | hashes to its id; it runs after every backup and on demand. |
| 211 | - **Fast start.** When the log grows large, grove writes a checkpoint (the in-memory state at |
| 212 | record N, checksummed) and replays only the records after it. The log itself is kept whole. |
| 213 | |
| 214 | ## Moving existing projects in |
| 215 | |
| 216 | Each repository's current history on Mononoke is read commit by commit, oldest first, and |
| 217 | written as points on main with the original messages, authors and times. Each imported point |
| 218 | records that it came from an import and the original commit hash. Open diffs on the old system |
| 219 | become changes in proposed state with one revision each. Nothing on the old system is removed |
| 220 | until the import of every project has been checked against it. |
| 221 | |
| 222 | ## Open decisions |
| 223 | |
| 224 | 1. **Shared ownership.** Today there are orgs. Should a project be owned by a group of |
| 225 | people, or by one person with co-owners? |
| 226 | 2. **Agents acting for someone else.** Should a person be able to lend their agent to |
| 227 | another project's owner (an agent working *for* the owner, run by someone else)? |
| 228 | 3. **Who runs previews.** Evidence can link to a running preview. Does grove host previews |
| 229 | itself (as Pages does today), or only link to what the agent runs? |
| 230 | 4. **Notifications.** How you hear that something needs your decision: web only, email, |
| 231 | or through your own agent. |
| 232 | 5. **Size limits.** A maximum file and revision size, so an agent can't fill the disk by mistake. |