grove
at @11
1# grove core model
2
3The objects grove stores, the rules they obey, and the operations people and agents perform.
4Read with [VISION.md](../VISION.md). Built in Rust with no dependencies beyond the standard library.
5
6## Overview
7
8```
9person ── 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
17A project's main line is a numbered sequence of snapshots. Work happens in changes. A change
18collects revisions of a stack of steps; the owner lands one revision, and its steps become the
19next 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
27recorded as *agent A for person P*. A person can revoke an agent, which kills its tokens and
28leaves its history intact.
29
30People can also act directly, from the web. Then the actor is just the person.
31
32## Projects
33
34A project has an owner (a person), a name unique within the owner, a description, and a
35visibility:
36
37- **public:** anyone can read it.
38- **private:** only the owner and the project's contributors can read it.
39
40Taking 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
42person in person. It lets someone join the server, and if it names a project, makes them a
43contributor to it. Nobody joins the server without one.
44
45Only the owner (and people they make co-owners) can land, decline, or change settings, and
46only in person: these are decisions, so an agent acting for the owner can't make them.
47
48## Content
49
50Content 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
59Names are UTF-8 and may not contain `/` or be `.` or `..`. Binary and text files are the same
60to storage; diffs are computed for text and shown as size changes otherwise.
61
62Ignored paths come from a `.groveignore` file in the project, same syntax as the usual ignore
63files. The CLI applies it; the server stores whatever it's given.
64
65## Main
66
67Main 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
78There are no branches and no merges on main. Work in progress lives in changes. Main only
79ever grows; a mistake is undone by landing a change that reverts it.
80
81Agents and the web refer to versions as `owner/project@n`, and `@main` for the latest.
82
83## Changes
84
85A 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```
100idea ──> 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
111Declined and withdrawn changes can be reopened. Nothing is ever deleted.
112
113### Revisions and steps
114
115A **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
125A **step** within a revision is a snapshot plus a message. Each step has a stable id inside
126the change, so the same step can be followed across revisions ("step 2 in r1, r2 and r4"),
127even as it's edited, reordered, split or dropped. Revising one step means creating a new
128revision; later steps carry forward, rebuilt on top of it if needed.
129
130This keeps Sapling's useful property, that amending is free and nothing is lost, without
131tracking each amend as its own object. The diff between two revisions (or between two
132versions of one step) shows exactly what the agent did in response to feedback.
133
134### Evidence
135
136Evidence 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
146grove stores evidence as given and renders what it knows how to: markdown, images, video,
147logs, tables, links to previews. Anything else downloads.
148
149### Discussion
150
151A change's discussion is one timeline of **messages** and **events**.
152
153A 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
155or line it's about. Messages can be edited; every version is kept.
156
157Events are written by grove: state changes, new revisions, evidence added, landing results.
158
159Steering is a message. The owner (or their agent) writes in the discussion; the building
160agent reads it and responds with a new revision, a reply, or both. grove doesn't interpret
161the request.
162
163## Landing
164
165grove never merges. A change lands only on the point it was built on.
166
1671. 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.
1692. 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
173Agents rewrite files; they don't need a merge algorithm to do it, and nothing in grove's
174history is ever a mix nobody wrote.
175
176## Agents' workflow
177
178From an agent's side, through the CLI or the API:
179
1801. `checkout owner/project`: files at `@main` in a working directory, with `.grove/`
181 holding local state.
1822. Edit files. `step "message"` records the working directory as a new step; `amend` replaces
183 the current step; `stack` shows the local steps.
1843. `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.
1864. `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.
1885. `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
194grove 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
216Each repository's current history on Mononoke is read commit by commit, oldest first, and
217written as points on main with the original messages, authors and times. Each imported point
218records that it came from an import and the original commit hash. Open diffs on the old system
219become changes in proposed state with one revision each. Nothing on the old system is removed
220until the import of every project has been checked against it.
221
222## Open decisions
223
2241. **Shared ownership.** Today there are orgs. Should a project be owned by a group of
225 people, or by one person with co-owners?
2262. **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)?
2283. **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?
2304. **Notifications.** How you hear that something needs your decision: web only, email,
231 or through your own agent.
2325. **Size limits.** A maximum file and revision size, so an agent can't fill the disk by mistake.