OXYGENHUB
# 445ce913ba56 oxygen / docs / guide.md
24.2 KiB · Markdown · last changed Guide: setting an agent up as a delegate, step by step 1 month ago renderedsource blame history raw
1
# Oxygen user guide
2
3
This guide is organised by task. It assumes the `oxy` binary is on your
4
path (`cargo build --release` puts it in `target/release/`). The design
5
behind every command is in [spec.md]spec.md; this guide only tells you
6
what to type and what to expect.
7
8
Oxygen tracks *changes* rather than snapshots: a change is the set of
9
edits you made, it commutes with unrelated changes, and conflicts are
10
recorded in history instead of blocking you. Two ideas recur below:
11
12
- A **bookmark** (default `main`) names a *state*: the set of changes it
13
  contains. A state can have several **heads** when independent changes
14
  have not been related to each other; that is normal, not a problem.
15
- The **working copy** is itself a change that Oxygen rewrites every time
16
  it looks at your files. `oxy commit` turns it into a real change.
17
18
Output uses colour and glyphs on a terminal; pipe it, pass `--plain`, or set
19
`NO_COLOR` for plain ASCII.
20
21
## 1. Set yourself up
22
23
```
24
oxy identity create --name "Ada Lovelace" --email [email protected]
25
oxy config --user core.sign change
26
```
27
28
`identity create` generates a composite Ed25519 + ML-DSA-65 key pair, keeps
29
the secret in `~/.config/oxy/keys/`, and records you as the default author
30
in `~/.config/oxy/config.toml`. With `core.sign = change` every commit is
31
signed; without it, changes are sealed in batches when you push (also
32
authenticated, just later). Use `--local` to record the identity in one
33
repository only.
34
35
`oxy identity show` prints the key, its expiry, delegates, and whether the
36
secret is available. `oxy identity export` writes the public identity to a
37
file to hand to whoever manages a trust policy (section 8).
38
39
## 2. Start a repository
40
41
```
42
mkdir project && cd project
43
oxy init
44
oxy status
45
```
46
47
`oxy init` creates `.oxy/`. Add `.oxyignore` (gitignore syntax, per
48
directory, negations with `!`) for files that should never be tracked;
49
`/target` and `*.tmp` are typical entries. As in Git, ignore rules only
50
affect untracked files: a file already in history stays tracked until you
51
delete it.
52
53
To take over an existing Git project, see section 12.
54
55
## 3. Everyday work
56
57
```
58
oxy status                 # what changed since the last commit
59
oxy diff                   # the edits, file by file
60
oxy commit -m "Add retry loop"
61
oxy log                    # newest first; -n 10, -v for full messages
62
oxy show 658990aa          # one change by ChangeId prefix, #hash prefix, or @
63
```
64
65
Commit part of what changed with `oxy commit PATH.. -m "..."` (only those
66
paths; the rest stays as uncommitted work) or `oxy commit -i -m "..."`, which
67
offers each hunk in turn like `git add -p`; a hunk that another chosen hunk
68
needs, such as the directory a new file lives in, comes along by itself.
69
70
`status --json` prints the same for tools, with every conflict's kind, path and
71
contributing ChangeIds. On a terminal, `status` lists each path with a mark: `✚ added`, `✎ modified`, `✖ removed`,
72
`➜ renamed`, `⚙ mode`. Renames are detected: a removed file and an added
73
file of the same kind whose contents are at least half alike are recorded
74
as a move of the same file, so identity survives a rename.
75
76
Files containing NUL bytes are stored whole ("opaque"); text files are
77
stored line by line and merge line by line. `.oxyattributes` overrides the
78
guess (section 14).
79
80
Every change has two names: a **ChangeId** (`658990aa…`) that survives
81
rewrites, and a content hash (`#7469d16c…`) that changes when the change is
82
edited. Use whichever you have; prefixes work.
83
84
## 3a. Bookmarks: branches without merges
85
86
A bookmark names a state; each workspace works on one (`main` to begin
87
with). Because changes commute, "merging" is the union of two bookmarks'
88
heads, with no merge change and any real conflict recorded as in section 6.
89
90
```
91
oxy bookmark create feature        # at the working copy's base
92
oxy bookmark switch feature        # check it out; commits now go here
93
oxy commit -m "Feature work"
94
oxy bookmark switch main
95
oxy merge feature                  # main now holds both; working copy follows if clean
96
oxy bookmark list                  # ● current, heads, ↑n/↓n against origin
97
oxy push -b feature                # publish a bookmark other than the current one
98
oxy bookmark set feature 658990aa  # move it; oxy bookmark delete feature to drop it
99
```
100
101
`oxy checkout feature` materialises a bookmark without switching to it, and
102
a bookmark name works wherever a change is expected when it has one head.
103
Each workspace remembers its own bookmark, so two directories can sit on
104
two bookmarks of one repository.
105
106
**A state is its heads, not a history.** If you are coming from Git, this
107
is the one thing to know. A bookmark holds its heads and what they depend
108
on, and a change depends only on the changes it builds on (the files it
109
touched), not on everything recorded before it. So on a bookmark with
110
several heads, a new bookmark at one change can be a much older-looking
111
tree. `oxy bookmark create NAME CHANGE` refuses when that would leave out
112
earlier work and lists what it would lack. Then pick one:
113
114
```
115
oxy bookmark create logo 745307b4 --upto  # everything here up to that change, as a Git branch would be
116
oxy bookmark create logo 745307b4 --only  # just that change and what it depends on
117
oxy bookmark create logo                  # the whole current state, usually what you want
118
```
119
120
`oxy bookmark switch` says when the bookmark you switch to lacks changes
121
that the one you left has.
122
123
### A develop / feature flow
124
125
Names with `/` work everywhere, pushing included:
126
127
```
128
oxy bookmark create feature/42-login          # at the working copy's base
129
oxy bookmark switch feature/42-login --carry  # started already? take the edits along
130
oxy commit -F msg.txt                         # a long message from a file
131
oxy push -b feature/42-login
132
oxy bookmark switch develop
133
oxy merge feature/42-login                    # the working copy follows when it was clean
134
oxy push                                      # develop, with the feature in it
135
oxy bookmark delete feature/42-login          # oxy push --delete feature/42-login on the remote
136
```
137
138
`bookmark switch` stops when the working copy has uncommitted edits, so
139
nothing is lost by accident. There are two ways past it:
140
- `--carry` takes the edits along. It is refused only when the target
141
  bookmark has other versions of the files they touch.
142
- `--discard` drops the edits.
143
144
`oxy describe` names what the working copy holds, for build stamps:
145
- a tag, when a tag holds exactly this state;
146
- otherwise the nearest tag with the distance from it and the change;
147
- or, with no tag behind it, the bookmark and the change;
148
- with `-dirty` added when there are uncommitted edits.
149
150
## 4. Fix up history
151
152
Oxygen keeps its own record of every operation, so all of these are safe to
153
try and to undo.
154
155
```
156
oxy describe -m "Better message"        # reword the head the working copy sits on
157
oxy describe 658990aa -m "..."          # reword any change; dependents are rewritten
158
oxy amend                               # fold the working copy into the head
159
oxy amend --into 658990aa               # fold it into an older change
160
oxy split 658990aa src/parser.rs        # split a head: those paths become a separate first change
161
oxy abandon 658990aa                    # drop a change; its content stays on disk
162
oxy squash 658990aa --into 9df9ac29     # fold a change into one it depends on
163
oxy rebase 658990aa --onto feature      # re-express a change against another base
164
oxy undo                                # undo the last operation, whatever it was
165
oxy op log                              # the operation log
166
```
167
168
Rewriting a change that others depend on rewrites them too, keeping their
169
ChangeIds. Because unrelated changes already coexist, `rebase` is rarely
170
needed; its job is to change what a change is said to build on, for instance
171
to drop a dependency on something you are about to abandon. Where a dependent referred to a line that no longer exists, the
172
reference is re-anchored to the nearest surviving line and `oxy abandon`
173
prints a note about it.
174
175
## 4a. Who wrote this, and what broke it
176
177
```
178
oxy blame src/parser.rs          # every line: the change that introduced it, author, age
179
oxy blame src/parser.rs --json
180
```
181
182
Blame is exact: each line is an atom that remembers the change that
183
introduced it, so renames cost nothing and there is no heuristic.
184
185
```
186
oxy bisect start
187
oxy bisect good v1.2             # a change, bookmark or tag known to be fine
188
oxy bisect bad main              # and one known to be broken
189
oxy bisect run make test         # exit 0 good, 125 skip, anything else bad
190
oxy bisect reset                 # back to where you were
191
```
192
193
Without `run`, each step checks out a state to test and you answer with
194
`oxy bisect good`, `bad`, or `skip`. The candidates are the changes the bad
195
state has that the good one lacks, ordered so every tested state is
196
well-formed; the answer is the first change whose inclusion breaks things.
197
198
## 5. Move around
199
200
```
201
oxy checkout                    # the bookmark's heads
202
oxy checkout 658990aa           # the state of one change and its dependencies
203
oxy checkout v1.2               # a tag
204
oxy checkout --discard ...      # throw away uncommitted work first
205
```
206
207
Checking out a single older change gives you the files that change and its
208
dependencies touch, not a full snapshot from that date: in Oxygen a change
209
depends only on what it actually builds on.
210
211
## 6. Conflicts
212
213
A conflict never stops a commit, fetch, or pull. It is recorded in the
214
state and shown by `oxy status`, which exits with status 2 while any
215
conflict remains. Conflicted text files are written with markers:
216
217
```
218
<<<<<<< oxy conflict 1 of 2, side 1: change k7m2p9 "Add retry loop"
219
... lines ...
220
======= oxy conflict 1 of 2, side 2: change q3x8w1 "Refactor client"
221
... lines ...
222
>>>>>>> oxy conflict 1 of 2 end
223
```
224
225
Edit the file to what it should be (keep one side, both, or neither) and
226
remove the markers. Tree conflicts, such as two files wanting the same
227
name, appear as `<name>.oxy-conflict-<n>` candidates: keep the one you
228
want under the right name and delete the rest.
229
230
```
231
oxy resolve      # which conflicts your edits resolve, which still have markers
232
oxy commit -m "Resolve retry vs refactor"
233
```
234
235
The commit is recorded as a *resolution*, a change that depends on every
236
side of the conflict; anyone who fetches it gets the conflict resolved.
237
238
## 7. Work with others
239
240
Run a server (any machine with the repositories under one directory):
241
242
```
243
oxy serve --root /srv/oxy --bind 0.0.0.0:8890 --token SECRET \
244
          --tls-cert cert.pem --tls-key key.pem
245
```
246
247
Without `--tls-*` the server speaks plain HTTP; keep that on a loopback
248
address or behind a TLS terminator. `--token` makes pushes require a bearer
249
token; reads stay open.
250
251
On your machine:
252
253
```
254
echo SECRET | oxy auth login https://host:8890 --with-token --ca my-ca.pem
255
oxy clone https://host:8890/project
256
cd project
257
oxy push
258
oxy pull
259
```
260
261
`--ca` is only for a server with a self-signed certificate. On a hub such as
262
OxygenHub, `oxy auth login https://oxygenhub.io` signs in through the
263
browser instead: it shows a code to approve on the hub, and the hub issues a
264
token that lasts 90 days past its last use.
265
266
### Signing in
267
268
`oxy auth login HUB` keeps one token per hub (scheme, host and port) in
269
`~/.config/oxy/credentials.toml`, readable by you only, and every repository
270
on that hub uses it: clone, fetch, push and subrepos alike. Nothing secret is
271
written into a checkout. `oxy auth status` lists the hubs you are signed in
272
to and checks each token with its hub; `oxy auth logout HUB` forgets the token
273
and revokes it on the hub.
274
275
To keep tokens in the system keyring instead, name a Git credential helper:
276
`oxy config --user auth.helper libsecret` (Linux), `osxkeychain` (macOS) or
277
`manager` (Windows). The file then keeps only the account and CA.
278
279
A token is found in this order: the remote's own `remote.<name>.token`, then
280
`$OXY_TOKEN` (for CI), then the store, then a remote on the same hub in an
281
enclosing checkout (so a subrepo uses its parent's). oxy sends a token over
282
`https` only, or over plain `http` to this machine; it is never sent to
283
another host after a redirect, and a credentials file that others can read
284
is ignored.
285
286
- `oxy fetch` updates `origin/main` without touching your bookmark.
287
- `oxy pull` fetches, then makes your bookmark the union of both sides.
288
  There is no merge commit; unrelated changes simply coexist, and a real
289
  conflict is recorded as in section 6. The working copy follows when it was
290
  clean.
291
- `oxy push` sends your changes and moves the remote bookmark. If the remote
292
  moved since you last fetched, the push is refused: pull and push again.
293
  `--force` overrides the compare-and-swap.
294
- `oxy remote add|remove|list` manages remotes; the clone source is `origin`.
295
296
A repository can also be cloned from a local path, which is handy for tests
297
and for a bare "hub" on a shared disk.
298
299
## 8. Decide who may publish: the trust policy
300
301
Without a policy a repository is *unverified*: everything is accepted and
302
every remote command says so. A policy names root keys and the keys allowed
303
to update each bookmark; servers and clients enforce it.
304
305
```
306
oxy policy init                    # your key becomes the only root; push publishes it
307
oxy policy show
308
oxy policy grant alice.identity    # alice.identity came from her `oxy identity export`
309
oxy policy grant ci.identity --pattern 'release/*'
310
oxy policy revoke 9458b656         # by KeyId prefix
311
oxy verify                         # signature status of every change in the log
312
```
313
314
Several people can hold root keys:
315
316
```
317
oxy policy root add bob.identity
318
oxy policy root threshold 2        # future updates need two root signatures
319
```
320
321
Once the threshold is above one, an update you make alone becomes a
322
*proposal file* (`policy-<serial>.proposal`, or `--out`). Send it to another
323
root holder, who runs `oxy policy sign policy-7.proposal`; whoever completes
324
the threshold applies it, and the next push publishes it. Everyone else
325
picks it up on fetch, verifying each step from the policy they already
326
trust.
327
328
## 9. Keys over time
329
330
```
331
oxy identity rotate                   # fresh key pair; old signatures stay valid
332
oxy identity renew --expires 365      # same keys, new expiry in days (0 = never)
333
oxy identity delegate add agent.identity --expires 8h       # sign changes for me today
334
oxy identity delegate add ci.identity --scope changes,refs  # sign and push for me
335
oxy identity delegate remove 3f2a…
336
oxy identity revoke 3f2a… --reason "agent host compromised" # a key you delegated to
337
```
338
339
Setting an agent up takes a home of its own, so it never holds your
340
secret key. In the agent's shell, inside any checkout:
341
342
```
343
export OXY_HOME=~/.oxy-agents/claude
344
oxy identity create --name "Claude Code on my laptop" --email [email protected]
345
#   prints the agent's KeyId; its identity file is keys/<KeyId>.identity in that home
346
oxy config --user author.name  "Your Name"      # the agent authors as you…
347
oxy config --user author.email "[email protected]"
348
oxy config --user author.key    <YOUR KeyId>
349
oxy config --user author.signer <THE AGENT's KeyId>   # …and signs as itself
350
oxy config --user core.sign     change
351
```
352
353
Then, in your own shell, in the repository: `oxy identity delegate add
354
~/.oxy-agents/claude/keys/<KeyId>.identity --scope changes --expires 8h`
355
(this rotates your identity; put the new KeyId in the agent's
356
`author.key`, and re-link it on your hub). Every commit the agent makes
357
carries you as author and its signature: `oxy log -r 'delegated()'`
358
lists them, `oxy verify` names the agent, and its own `oxy push` is
359
refused under any policy, so you land its work.
360
361
```
362
oxy identity revoke                   # retire your own key
363
oxy identity revoke 3f2a… --reason "laptop stolen"   # any key, if you hold a root
364
```
365
366
Rotation, renewal, and delegation all issue a successor identity that names
367
the previous one. If a policy names your key, it is updated to the successor
368
in the same step (as a proposal when more root signatures are needed).
369
Changes signed before the rotation remain authorised; a revoked key
370
authorises nothing. `oxy verify` shows signatures by revoked keys as valid
371
but revoked.
372
373
## 10. Tags and workspaces
374
375
```
376
oxy tag create v1.2 -m "First release"   # the current heads; or name changes
377
oxy tag list
378
oxy workspace add ../project-hotfix      # a second checkout sharing the repository
379
oxy workspace list
380
oxy workspace forget ../project-hotfix
381
```
382
383
A tag is an immutable state, signed when you have a key, and travels with
384
push and fetch. Workspaces have their own working copy and bookmark
385
(`workspace.bookmark`) but share objects and history.
386
387
### Receipts from CI
388
389
A runner such as OxygenFlow signs a receipt for every job it runs: what ran,
390
in what environment, on which files, and the result. Receipts are kept with
391
the state they ran on.
392
393
```
394
oxy receipt list                         # receipts for the bookmark's state
395
oxy receipt show 3f2a9c                  # one in full, with its signature checked
396
oxy receipt export 3f2a9c build.receipt  # hand it to someone
397
oxy receipt verify build.receipt         # check one without keeping it
398
oxy receipt import build.receipt         # verify and keep it
399
```
400
401
## 11. Big repositories: partial, sparse, shallow
402
403
```
404
oxy clone URL --max-blob 1M          # files above 1 MiB are fetched on first read
405
oxy clone URL --sparse src --sparse docs   # materialise only these subtrees
406
oxy clone URL --depth 50             # only the last 50 levels of history
407
oxy sparse src docs tests            # change the filter later
408
oxy deepen 100                       # fetch more history below the boundary
409
```
410
411
Lazy blobs and sparse trees do not affect commits: what you do not have is
412
left exactly as it was. A shallow clone starts from a *checkpoint* of the
413
state, so status and commit work at full speed from the first minute.
414
415
## 11a. Nested repositories (submodules)
416
417
A subrepo is another repository checked out at a path and pinned to a state,
418
Oxygen or Git. The pin is versioned like a file, so two people pinning
419
different states get an ordinary conflict.
420
421
```
422
oxy submodule add https://host/lib.git libs/lib     # Git or Oxygen URL; records .oxymodules
423
oxy commit -m "Add lib"                              # the pin is committed with the parent
424
oxy submodule status                                 # pin vs what is on disk
425
oxy submodule update --init                          # after a clone: fetch nested repositories
426
oxy submodule remove libs/lib                        # drop the node; the directory stays
427
```
428
429
Committing the parent records whatever the nested repository is at, and
430
checking out an older parent state moves the nested repository back, unless
431
it has uncommitted changes, which are never overwritten. An absent nested
432
repository keeps its pin until you run `update --init`. Git submodules
433
survive `oxy git import` as Git pins and go back out as gitlinks on export.
434
435
## 12. Git interoperability
436
437
```
438
oxy git import ../project.git ./project-oxy --branch main   # convert a Git history
439
oxy git fetch https://github.com/you/project.git            # bring new Git commits in
440
oxy git push  https://github.com/you/project.git            # publish your changes to Git
441
oxy git export ../mirror.git                                # a plain Git mirror of the bookmark
442
```
443
444
Import turns each commit into one change (merge commits become
445
resolutions), keeps authors and hashes as `Git-Author`/`Git-Commit`
446
trailers, detects renames, and skips submodules. `git fetch`/`git push` keep
447
a bare mirror under `.oxy/git/`; a push is refused until the remote's tip
448
has been fetched, so it always fast-forwards. Exported commits carry
449
`Change-Id` and `Oxy-Hash` trailers.
450
451
## 13. Automate: hooks
452
453
Executables in `.oxy/hooks/<event>` (never cloned) receive one JSON object
454
on stdin. `pre-commit`, `pre-push` and other `pre-*` hooks veto by exiting
455
non-zero, and their stderr becomes the error message. Events:
456
`pre-commit`, `post-commit`, `pre-push`, `post-fetch`, `post-checkout`, and
457
`semantic-deps`, which runs at commit when `hooks.semantic-deps = true` and
458
answers `{"deps": ["<hash>", …]}` to make the change depend on more than it
459
textually references.
460
461
## 14. Per-path attributes, ignores, locks
462
463
`.oxyattributes` uses gitattributes syntax:
464
465
```
466
*.png     binary
467
*.txt     text eol=lf
468
*.psd     lockable nocompress
469
```
470
471
`binary`/`text` override content sniffing, `eol=lf|crlf` normalises line
472
endings in the store and on checkout, `nocompress` stores chunks raw, and
473
`lockable` files are checked out read-only until you take the local lock:
474
475
```
476
oxy lock assets/logo.psd
477
oxy unlock assets/logo.psd
478
```
479
480
Locks are advisory and local; server-side locks are planned.
481
482
## 15. Configuration
483
484
Two files, both `key = "value"` lines: `~/.config/oxy/config.toml`
485
(`oxy config --user KEY VALUE`) and `.oxy/config` (`oxy config KEY VALUE`).
486
The repository file wins. `OXY_HOME` relocates the user directory. `OXY_TIMINGS=1` prints how long each phase of a snapshot takes (loading the base state, rendering it, reading the working copy, building the change) to standard error, for finding out where a slow command spends its time.
487
488
| Key | Meaning |
489
|---|---|
490
| `author.name`, `author.email`, `author.key` | who you are; `key` is set by `identity create` |
491
| `author.signer` | the key that signs instead of `author.key`: an agent's own key, which your identity lists as a delegate (also `$OXY_SIGNER`) |
492
| `core.sign` | `change` to sign every commit; otherwise seals at push |
493
| `core.op-retention` | days for which old operations keep their states alive (90) |
494
| `core.gc-grace` | days an unreachable object survives (14) |
495
| `core.checkpoint-interval` | changes between automatic checkpoints (256) |
496
| `filter.max-blob`, `filter.paths` | lazy blobs and sparse subtrees (section 11) |
497
| `hooks.semantic-deps` | run the semantic-deps hook at commit |
498
| `workspace.bookmark` | this workspace's bookmark (`main`) |
499
| `remote.<name>.url`, `.token`, `.ca` | remotes; token and CA fall back to `OXY_TOKEN` and `OXY_CA`, then `oxy auth login`'s store |
500
| `auth.helper` (user file) | a Git credential helper to keep `oxy auth` tokens in, such as `libsecret` |
501
502
## 16. Shell prompt
503
504
`oxy prompt` prints `main 658990aa*+2` (bookmark, working copy, `*` dirty,
505
`!` conflicted, ahead/behind origin) in a few milliseconds and records
506
nothing. See [prompt.md]prompt.md for bash, oh-my-bash, zsh, and fish
507
snippets and the `--format` placeholders.
508
509
## 17. Housekeeping
510
511
```
512
oxy gc              # prune unreachable objects past the grace period, repack into one pack
513
oxy gc --now        # ignore the grace period (only when nothing else is writing)
514
oxy fsck            # check every object, the op log, references, signatures, checkpoints
515
oxy doctor          # check identity, config and remotes, with what to do about each problem
516
oxy checkpoint      # serialise the bookmark's state so large histories load instantly
517
```
518
519
Both run automatically: packing when loose objects pile up, checkpoints
520
every `core.checkpoint-interval` changes.
521
522
## 17a. Scripting and editors
523
524
Every reporting command takes `--json` (`status`, `log --path`, `show`,
525
`diff`, `hunks`, `blame`, `resolve`, `op log`, `bookmark list`, `remote list`,
526
`fetch`, `pull`, `push`, `submodule status`, `bisect status`, `verify`,
527
`policy show`, `root`, `version`). `oxy cat PATH -r CHANGE` prints a file as
528
it was, `oxy discard PATH..` restores files to the base, and `oxy hunks`
529
numbers the working copy's hunks for `commit --hunks` and `discard --hunks`.
530
531
## 18. If something looks wrong
532
533
- **"the remote has change … which you do not; pull first"**: someone
534
  pushed since your last fetch. `oxy pull`, then push.
535
- **"policy needs N root signature(s)"** on fetch: the server's policy was
536
  updated in a way your copy cannot verify. Ask a root holder whether the
537
  chain of updates was published; a fresh clone trusts the current policy.
538
- **`oxy status` exits 2**: conflicts remain; see section 6.
539
- **Many heads on `main`**: independent changes that do not reference one
540
  another. Nothing to fix; the state is their union.
541
- **Undo anything**: `oxy undo` reverts the last operation, including
542
  pulls, checkouts, and rewrites.
543
544
## Shell completions and man pages
545
546
```sh
547
oxy completions bash > ~/.local/share/bash-completion/completions/oxy   # bash
548
oxy completions zsh > "${fpath[1]}/_oxy"                                 # zsh
549
oxy completions fish > ~/.config/fish/completions/oxy.fish               # fish
550
oxy man --dir ~/.local/share/man/man1                                    # then: man oxy-commit
551
```
552
553
The oh-my-bash plugin in `contrib/oh-my-bash` loads the bash completions itself.