Name
lane Copy-on-write worktrees with memory that survives them.
git worktree add hands you a clean tree and nothing git ignores. So you reinstall, you rebuild cold, and everything the last branch taught you about src/auth.rs stays in the last branch. lane new hands you the same tree in seconds by sharing the bytes on disk, and brings the notes with it.
$ curl -fsSL https://lane.lukeed.com | shWorkflow
Five commands, start to finish. The panel is the disk — one set of blocks, and every tree pointing at it. Beside it is what you type to get there.
main/
by reference
fix-login/
agent-b/
agent-c/
shared with main/lane changes
memory
.lane/memory/
01lane new
A second worktree in seconds, at no cost on disk.
$ lane new fix-loginreflink: yes (clonefile)12283 files cloned (1358.1 MiB, 0 copied)~/repo/.lane/trees/fix-login$ ls node_modules | wc -l4612$ cargo buildFinished `dev` profile in 0.21sEvery path git ignores, at any depth, arrives as a window onto blocks main/ already holds. Nothing was copied, so the build cache is warm on the first command.
02lane note
Write down what must stay true, beside the code it constrains.
$ lane note add src/auth.rs -a "fn verify" \> "early return leaks token length"noted -> src/auth.rs#fn verify$ $EDITOR src/auth.rs$ git commit -am "make verify constant-time"[fix-login 4f1a92c] 1 file changedThe edit un-shares that one file, and only inside the lane — main/ still reads the block it always read. Copy costs nothing until you write, and then it costs one file.
03lane check
Anchored to the symbol, flagged the moment the symbol moves.
$ cargo buildCompiling myapp v0.1.0Finished `dev` profile in 10.2s$ lane checkfresh 7content-changed 1contract-changed 0anchor-missing 0The rebuild rewrote 550 files. Those blocks belong to the lane and leave with it. The note noticed too: fn verify kept its signature and changed its body, so lane flags it rather than guess whether it is still true.
04lane ls
One repo, three agents, nothing to lock and nothing to merge.
$ lane new agent-b && lane new agent-c2 lanes cloned (0 copied)$ lane lsfix-login open dirty 1 pending note(s)agent-b open dirty 3 pending note(s)agent-c open clean 0 pending note(s)Three trees, three warm caches, one set of extents. Each agent writes only the blocks it touches, and every note is a new file — so two of them can annotate the same function in the same second.
05lane merge
The worktree goes. What it learned lands on main.
$ lane note replace 01M0B4KQTX7H3EZ8FE7S6BJ91N \> "constant-time; no early return"replacement queued -> 01M0B4KQTX7H3EZ8FE7S6BJ91N src/auth.rs#fn verify$ lane merge # in each lane, in any orderrebased onto mainmemory: +1 new; checked 87 fresh, 1 content-changed0 contract-changed, 0 anchor-missingcommitted memory updatefast-forwarded mainremoved lane fix-loginThe commits land, the worktrees close, and every block they allocated is freed. The note does not close with them: the drifted one was rewritten against the code that shipped, the old one moved to .lane/attic/, and the next lane opens with both in reach.
Where trunk is protected, lane push rebases, audits, commits memory, and pushes the lane for the pull request. The lane waits on disk; lane ls marks it pushedwhile the remote has its tip, then landed once trunk carries its landing record, and lane prune removes it. That record is tree content rather than a commit, so neither a squash nor a rebase merge can hide it.
Description
A second worktree that costs no disk and no cold build.
Lane asks git for every path it ignores — including nested ones like packages/a/node_modules and single files like .env — and clones each one by reference. On a filesystem with reflink support the two copies point at the same extents, so the second tree occupies no new space and the build cache is already warm.
| In the new tree | git worktree add | lane new |
|---|---|---|
| tracked files | checked out | checked out |
| node_modules, at any depth | absent | exists, by reference |
| target/, dist/, .venv/ | absent | exists, by reference |
| .env and the rest of .gitignore | absent | exists, by reference |
| uncommitted work | absent | with --dirty |
| what earlier branches learned | absent | lane why <file> |
| what those 12,283 files cost | 1358.1 MiB, then npm i and a cold build | 0 B, 4.0s |
That is the copy half. The write half: editing a file in the lane un-shares only that file's blocks. One source edit here, plus the incremental rebuild it triggered, rewrote 550 files — 106.5 MiB. The other 1251.6 MiB stayed shared, and the 106.5 went away with the lane. Your edit is free. The rebuild is what costs, and it does not outlive the worktree.
Reflink works on APFS, btrfs, XFS with reflink=1, bcachefs and recent ZFS. Everywhere else lane init says so up front and lane new leaves you a plain worktree — byte-copying a build cache is the expensive thing lane exists to avoid. Opt a path out with git config --add lane.exclude target.
Memory
The reason you did it that way, kept next to the code it constrains.
A note costs one command and takes no taxonomy decision. Write down what must stay true, not what you changed.
$ lane note add src/auth.rs -a "fn verify" \ "must stay constant-time; early return leaks token length" noted -> src/auth.rs#fn verify $ lane why src/auth.rs [fn verify] - 01M0B9MBYB · 2026-08-14 must stay constant-time; early return leaks token length
Or leave it in the commit you were already writing. lane install hooks reads one trailer and nothing else — no diff, no commit body, no history import:
$ git commit -am "make verify constant-time"
...
Why: src/auth.rs#fn verify | early return leaks token lengthThe subject says what you did. The trailer says what must stay true. Six months later the first is history and the second is still load-bearing.
Anchors — what the note is about
| fn verify | a declaration, by keyword and name |
|---|---|
| verify | any declaration of that name |
| #script, #style | a top-level block in .svelte, .vue, html |
| ## Rate limiting | a markdown section |
| @file | the whole file — the default |
Tree-sitter resolves these, not a regular expression. A span ends where the declaration ends, a # inside a code fence is not a heading, and a brace inside a string truncates nothing.
Freshness — what happens as the code moves
| mark | tier | meaning | what lane does |
|---|---|---|---|
| · | fresh | The span is unchanged. | Nothing happens, and it cost nothing to know. |
| ~ | content-changed | The implementation moved, the contract held. | Flagged until you resolve it. |
| ! | contract-changed | The thing being described changed shape. | Flagged until you resolve it. |
| x | anchor-missing | The symbol is gone. | Moved to .lane/attic/, recoverable with git mv. |
Each anchor is hashed on its own span, normalized, so running a formatter stales nothing and editing #script leaves a note on #style alone. A moved or renamed file is followed, not evicted — lane audit reads git's own rename detection. A drifted note keeps getting reported until you confirm, replace, or retire it.
Agents
The next agent reads the constraints before it edits.
Two agents can annotate the same file, and the same anchor, in the same second. A note text is written as a new file, so independent findings never lock the same bytes. Land them in any order; both notes are there afterwards.
$ lane why src/auth.rs
[fn verify]
- 01M0B9MBYB · 2026-08-14
must stay constant-time; early return leaks token length
- 01M0B9MZQ4 · 2026-08-14
the token bytes must never reach a log, even at debuglane init writes a four-line protocol into AGENTS.md so the notes are already in context. Notes are plain markdown at predictable paths — an agent finds them with no tool integration at all, which is the reason they are files and not a sidecar database.
Installation
Get the binary — pick one
$ curl -fsSL https://lane.lukeed.com | shmacOS and Linux, arm64 and x86_64. Lands in ~/.local/bin; set LANE_INSTALL to put it elsewhere, LANE_VERSION to pin a release. Read it first at lane.lukeed.com/install.sh.$ cargo binstall --git https://github.com/lukeed/lane laneThe same prebuilt binary, if you already run binstall.$ cargo install --git https://github.com/lukeed/laneBuilds from source. Rust 1.85 or newer, edition 2024.Once per machine
$ eval "$(lane shellenv)"Put it in .zshrc or .bashrc. It is what makes lane new leave you inside the lane instead of printing its path.Once per repository
$ cd yourproject && lane initScaffolds .lane/, adds the union merge rule and the agent protocol, and tells you whether this filesystem does reflink. Commit what it writes.$ lane install hooksCaptures Why: trailers from commit messages into notes. Worktrees share a hooks directory, so this covers every lane. Optional.$ lane install skillWrites the fuller agent workflow to .agents/skills/lane/SKILL.md, loaded only when an agent is doing lane work. Optional; lane init already leaves a short protocol in AGENTS.md.Lane never calls a model. lane merge is local and deterministic, so it works the same way on a plane.
$ cargo run -p lane-tour -- start
builds a throwaway repo next to this checkout and prints its pathThe tour drives the real workflows — a note drifting, three lanes landing out of order, two landings colliding — and prints every command before it runs, so you finish having seen the ones you would actually type.
See also
- usageevery command, in the order you meet them
- commandsevery flag, one entry each
- memorycapturing decisions from commit trailers
- github.com/lukeed/lanethe source