Name
lane Copy-on-write Git worktrees with memory that survives them.
git worktree add gives you a clean checkout and none of the ignored files that make it fast: node_modules, target/, .env. You reinstall and rebuild from scratch, even though those caches are already on disk. Whatever the last branch learned about a file stays in that branch.
lane new clones ignored paths by reference, sharing bytes on disk. The new tree starts quickly with its prewarmed cache(s).
$ curl -fsSL https://lane.lukeed.com | shWorkflow
Left is the disk: one set of blocks, every tree pointing at it. Right is what you type.
main/
by reference
fix-login/
agent-b/
agent-c/
shared with main/lane changes
memory
.lane/memory/
01lane new
Ignored files arrive by reference. The cache is already warm.
$ 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.21s12,283 files, 1358.1 MiB shared, 0 copied. node_modules is there. cargo build finishes in 0.21s because target/ was never missing.
02lane note
Record what must stay true. Then edit.
$ 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 note is a new file. Editing src/auth.rs un-shares only that file. main still reads the original block.
03lane check
The rebuild stays in the lane. lane check flags the body.
$ cargo buildCompiling myapp v0.1.0Finished `dev` profile in 10.2s$ lane checkfresh 7content-changed 1contract-changed 0anchor-missing 0550 files rewritten — 106.5 MiB now owned by the lane. fn verify kept its signature and changed its body, so the note is content-changed.
04lane ls
Three worktrees. One set of extents.
$ 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)Each lane writes only the blocks it touches and carries its own memory notes. Every note is its own file, so two agents can annotate the same function at once.
05lane merge
The worktree is removed. Commits and notes stay.
$ 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 114.9 MiB the lanes allocated is freed. The drifted note was rewritten; the old one is in .lane/attic/.
If trunk is protected, use lane push instead of lane merge. After the pull request lands, lane prune removes the worktree.
Description
A second worktree that costs no disk and no cold build.
Lane asks git for every ignored path — nested ones like packages/a/node_modules, single files like .env — and clones each by reference. On APFS, btrfs, and XFS with reflink=1, the two copies share the same extents. The second tree takes 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 |
Edit a file and only that file's blocks un-share. One source edit plus its incremental rebuild rewrote 550 files — 106.5 MiB. The other 1251.6 MiB stayed shared. The 106.5 MiB goes away when the lane does.
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 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 records 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 the Why: 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.
Anchors — what the note is about
| fn verify | a declaration, by kind and name |
|---|---|
| verify | a uniquely named declaration |
| #script, #style | a top-level block in .svelte, .vue, html |
| ## Rate limiting | a markdown section |
| @file | the whole file (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.
Freshness — what happens as the code moves
| mark | tier | meaning | what lane does |
|---|---|---|---|
| · | fresh | The anchored span is unchanged. | Nothing. |
| ~ | content-changed | The implementation changed. | Flagged until you resolve it. |
| ! | contract-changed | The declaration changed. | Flagged until you resolve it. |
| x | anchor-missing | The symbol no longer resolves. | Moved to .lane/attic/. |
Each anchor is hashed on its own span, normalized, so a formatter stales nothing. Editing #script leaves a note on #style alone. A renamed file is followed — lane audit reads git's own rename detection. A drifted note stays flagged until you confirm, replace, or retire it.
Agents
The next agent reads the constraints before it edits.
Two agents can note the same file, and the same anchor, at the same time. Each note is a new file, so nothing locks. 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. Notes are plain markdown at predictable paths, so an agent finds them with no plugin.
Installation
Get the binary
$ 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. Without it, lane new prints a path instead of moving you into the lane.Once per repository
$ cd yourproject && lane initScaffolds .lane/, adds the union merge rule and the agent protocol, and reports whether this filesystem does reflink. Commit what it writes.$ lane install hooksCaptures Why: trailers from commit messages. Worktrees share a hooks directory, so this covers every lane. Optional.$ lane install skillWrites the fuller agent workflow to .agents/skills/lane/SKILL.md. Optional; lane init already leaves a short protocol in AGENTS.md.$ cargo run -p lane-tour -- start
builds a throwaway repo next to this checkout and prints its pathThe tour runs the real workflows — a note drifting, three lanes landing out of order, two landings colliding — and prints every command before it runs.
See also
- usagethe daily loop
- commandsevery flag
- memoryDecisions & Constraints
- github.com/lukeed/lanesource