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 | sh

Workflow

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.

diskone treesharedcopy-on-writelane changesthree laneslandedfreed

main/12,283 files

by reference

fix-login/

agent-b/

agent-c/

shared with main/lane changes

memory

.lane/memory/nothing noted yet1 note · fix-login1 note · content-changed4 notes · 3 lanes4 on main · 1 in the attic

1358.1 MiB shared0 B writtena byte copy: 1358.1 MiB
1358.1 MiB shared12 KiB writtenone file left the shared set
1358.1 MiB shared106.5 MiB writtenthe rebuild, not your edit
1358.1 MiB shared114.9 MiB writtenthree lanes, one set of extents
1358.1 MiB shared3 commits landedrebased, then fast-forwarded
1358.1 MiB shared114.9 MiB freedthe commits and the notes stayed
  1. 01lane new

    A second worktree in seconds, at no cost on disk.

    zsh — ~/repo1 of 5
    $ lane new fix-login
    reflink: yes (clonefile)
    12283 files cloned (1358.1 MiB, 0 copied)
    ~/repo/.lane/trees/fix-login
    $ ls node_modules | wc -l
    4612
    $ cargo build
    Finished `dev` profile in 0.21s

    Every 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.

  2. 02lane note

    Write down what must stay true, beside the code it constrains.

    zsh — ~/…/fix-login2 of 5
    $ 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 changed

    The 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.

  3. 03lane check

    Anchored to the symbol, flagged the moment the symbol moves.

    zsh — ~/…/fix-login3 of 5
    $ cargo build
    Compiling myapp v0.1.0
    Finished `dev` profile in 10.2s
    $ lane check
    fresh 7
    content-changed 1
    contract-changed 0
    anchor-missing 0

    The 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.

  4. 04lane ls

    One repo, three agents, nothing to lock and nothing to merge.

    zsh — ~/repo4 of 5
    $ lane new agent-b && lane new agent-c
    2 lanes cloned (0 copied)
    $ lane ls
    fix-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.

  5. 05lane merge

    The worktree goes. What it learned lands on main.

    zsh — ~/…/fix-login5 of 5
    $ lane note replace 01M0B4KQTX7H3EZ8FE7S6BJ91N \
    > "constant-time; no early return"
    replacement queued -> 01M0B4KQTX7H3EZ8FE7S6BJ91N src/auth.rs#fn verify
    $ lane merge # in each lane, in any order
    rebased onto main
    memory: +1 new; checked 8
    7 fresh, 1 content-changed
    0 contract-changed, 0 anchor-missing
    committed memory update
    fast-forwarded main
    removed lane fix-login

    The 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 treegit worktree addlane new
tracked fileschecked outchecked out
node_modules, at any depthabsentexists, by reference
target/, dist/, .venv/absentexists, by reference
.env and the rest of .gitignoreabsentexists, by reference
uncommitted workabsentwith --dirty
what earlier branches learnedabsentlane why <file>
what those 12,283 files cost1358.1 MiB, then npm i and a cold build0 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 length

The 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 verifya declaration, by keyword and name
verifyany declaration of that name
#script, #stylea top-level block in .svelte, .vue, html
## Rate limitinga markdown section
@filethe 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

marktiermeaningwhat lane does
·freshThe span is unchanged.Nothing happens, and it cost nothing to know.
~content-changedThe implementation moved, the contract held.Flagged until you resolve it.
!contract-changedThe thing being described changed shape.Flagged until you resolve it.
xanchor-missingThe 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 debug

lane 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 path

The 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