You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A worktree's node_modules is its own directory of links, never a link to the parent's: an agent's install stays in its checkout (#1262)
Linking the parent's node_modules directory made it the worktree's install in
pnpm's eyes: an agent that changed a dependency and ran pnpm install had pnpm
resolve through the link and rewrite the parent checkout's workspace links —
or, under CI=true, purge its node_modules outright — and every later agent
died at boot. The worktree now gets a real directory per dependency tree,
holding one link per entry, with the package manager's private state (.pnpm,
.modules.yaml — every dot-entry but .bin) left out. Packages still resolve:
an entry is a relative link into .pnpm, and a link to it resolves where the
target lives. A real directory is covered by the repo's own node_modules/
ignore rule, so the slash-free exclude (#738) goes with it.
Copy file name to clipboardExpand all lines: FEATURES-SPEC.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -103,7 +103,7 @@ happens while nobody is at the keyboard.
103
103
## Handoff and what lands in git
104
104
105
105
- Every agent gets its own git worktree and branch; your checkout is never touched
106
-
- Dependency directories shared from the parent checkout instead of reinstalled
106
+
- Dependency directories shared from the parent checkout instead of reinstalled — as directories of links, so an agent's own install stays in its checkout and never rewrites or purges the parent's
107
107
- A checkout whose work is not on the remote is kept — and a publish-nothing (`handoff: local`) agent's is kept until you publish or delete it
Copy file name to clipboardExpand all lines: packages/framework/src/daemon-runtime.SPEC.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,7 +48,7 @@ The user runs several agents on the same project at once, and keeps working in t
48
48
49
49
Each agent is given its own git worktree under the project's `.the-framework/branches/`, on its own `tf-agent-<agent id>` branch. Concurrent agents on one project therefore never fight over a working tree, and the user's own checkout — uncommitted work included — is left untouched.
50
50
51
-
A fresh worktree has no installed dependencies, since those are not tracked by git, so the project's are linked in and the links are made invisible to git.
51
+
A fresh worktree has no installed dependencies, since those are not tracked by git, so the project's are mirrored in (`store/worktree-deps`).
Copy file name to clipboardExpand all lines: packages/framework/src/store/worktree-deps.SPEC.md
+23-15Lines changed: 23 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,10 +1,16 @@
1
-
Gives a fresh worktree a working dependency tree, so an agent can run the project's commands the moment it starts, and keeps that tree invisible to git so the agent never commits it.
1
+
Gives a fresh worktree a working dependency tree, so an agent can run the project's commands the moment it starts — shared with the parent checkout, yet safe for the agent to install into.
2
+
3
+
## User story
4
+
5
+
- The user starts an agent and it works immediately: it can run the tests, the build, the linter. It does not sit for a minute installing, and starting ten agents does not fill the disk.
6
+
- An agent changes a dependency and installs. That install must land in the agent's own checkout — never rewrite, and never delete, the dependencies of the user's checkout, which every later agent is handed.
2
7
3
8
## Business logic — TL;DR
4
9
5
-
-**Dependencies are shared, not copied** - the parent checkout's `node_modules` directories are linked into the worktree at the same relative paths, instantly and at no extra disk cost.
6
-
-**Workspace packages get their own link** - every `node_modules` down to two levels below the repo root is linked, so a monorepo's packages work too.
7
-
-**The links are hidden from git** - the repo is told to ignore them, or the agent would commit dangling links onto its branch and into its pull request.
10
+
-**Dependencies are shared, not copied** - each of the parent checkout's dependency directories is mirrored into the worktree at the same relative path, instantly and at no extra disk cost.
11
+
-**A real directory of links, never a linked directory** - the worktree gets a directory of its own holding one link per dependency entry, so an install inside the worktree writes into the worktree.
12
+
-**The package manager's private state stays behind** - the entries that mark a tree as the package manager's own install are not linked; the packages resolve without them.
13
+
-**Workspace packages get their own tree** - every dependency directory down to two levels below the repo root is mirrored, so a monorepo's packages work too.
8
14
-**Never fatal** - a link that cannot be made is skipped; a worktree with missing dependencies is a worse agent, not a failed one.
9
15
10
16
## Business logic
@@ -13,33 +19,35 @@ Gives a fresh worktree a working dependency tree, so an agent can run the projec
13
19
14
20
#### User story
15
21
16
-
The user starts an agent and it works immediately: it can run the tests, the build, the linter. It does not sit for a minute installing, and starting ten agents does not fill the disk.
22
+
See `## User story`: the agent works immediately, and ten agents cost no extra disk.
17
23
18
24
#### Business logic
19
25
20
-
Dependency directories are ignored by git, so a new worktree is handed an empty one and every command in it fails. Instead of copying or installing, the parent checkout's dependency directories are linked into the worktree at the same relative paths. Anything already present at a link's location is left alone, since the agent may have installed for itself already, and any missing parent directory is created first.
26
+
Dependency directories are ignored by git, so a new worktree is handed an empty one and every command in it fails. Instead of copying or installing, each of the parent checkout's dependency directories is mirrored into the worktree at the same relative path: a real directory is created there, and inside it one link per entry of the parent's directory, pointing at that entry. A tree already present in the worktree is left alone, since the agent may have installed for itself already, and any missing parent directory is created first.
21
27
22
-
The scan looks for a dependency directory at the repo root and at every directory down to two levels below it, which covers a workspace's per-package dependencies without walking the whole tree. Dependency directories, the git directory, the framework's own directory, build outputs and dot-directories are never descended into. The result is ordered, so linking happens in a stable order.
28
+
The scan looks for a dependency directory at the repo root and at every directory down to two levels below it, which covers a workspace's per-package dependencies without walking the whole tree. Dependency directories, the git directory, the framework's own directory, build outputs and dot-directories are never descended into. The result is ordered, so mirroring happens in a stable order.
23
29
24
30
#### Rationale
25
31
26
-
Three options existed: copy the tree (correct, but gigabytes per agent), install into each worktree (correct, but real waiting on every start), or link the parent checkout's trees in (instant, no extra disk, one store shared by all agents). Linking wins. The one case it is wrong for is an agent that changes the dependency manifest — and that agent needs its own install anyway, which it runs itself.
27
-
28
-
Whole directories are linked, rather than their contents, because that is what makes a workspace resolve: the links inside a package's dependency directory still point at their real location in the parent checkout.
32
+
Three options existed: copy the tree (correct, but gigabytes per agent), install into each worktree (correct, but real waiting on every start), or link the parent checkout's trees in (instant, no extra disk, one store shared by all agents). Linking wins.
29
33
30
-
### The links are hidden from git
34
+
### A real directory of links, never a linked directory
31
35
32
36
#### User story
33
37
34
-
The agent's pull request contains its work and nothing else — no dependency directories, no broken links pointing at a path that only exists on the machine the agent ran on.
38
+
See `## User story`: an agent's install must stay in the agent's checkout.
35
39
36
40
#### Business logic
37
41
38
-
A repo's ignore rules normally name the dependency directory with a trailing slash, which matches a real directory only. The linked trees are links, not directories, so those rules do not cover them and they show up as untracked in every agent's worktree. That matters because the agent stages everything it changed, so it would commit those links onto its branch and into its pull request.
42
+
The worktree's dependency directory is a directory of its own, not a link to the parent's. When an agent installs in its worktree — which an agent that changes a dependency must — the package manager rewrites the entries of the worktree's directory and leaves the parent checkout's untouched. After the worktree is removed, the parent checkout's dependencies are exactly as they were.
43
+
44
+
The package manager's private state — every dot-entry of a dependency directory except the executables directory — is not linked. The executables directory is, because an agent runs the project's tools.
45
+
46
+
#### Rationale
39
47
40
-
A rule without the trailing slash is therefore added to the repo's own local exclusions, covering the link form in every worktree. The main checkout is unaffected, because its dependency directory is a real directory already ignored under the same name.
48
+
Linking the directory itself made the parent's tree the worktree's install in the package manager's eyes: it resolved through the link, rewrote the parent checkout's workspace links to point into the worktree — which dangled once the worktree was removed — or, when told it was running unattended, purged the parent's tree outright. Either way every later agent died at boot. The private state is what tells the package manager "this tree is mine, installed here", so it stays out of the worktree; the packages resolve without it, because a package entry is itself a relative link into the package manager's store, and a link to that link resolves where the target lives — in the parent checkout.
41
49
42
-
This is best-effort as well: on a project that is not a git repo, or where the exclusion cannot be written, the links simply stay visible to git status.
50
+
Since the worktree's dependency directory is a real directory, the repo's own ignore rule for dependency directories covers it, and git never sees the links.
Copy file name to clipboardExpand all lines: packages/framework/src/store/worktree-deps.test.SPEC.md
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,10 +1,10 @@
1
-
What the tests cover: giving a worktree its dependencies and hiding them from git.
1
+
What the tests cover: giving a worktree its dependencies.
2
2
3
3
- The scan finds the repo root's dependency tree and each workspace package's, and never descends into a dependency tree, the git directory, or the framework's own directory.
4
-
- Each tree is linked into the worktree at the same relative path, creating any missing parent directory; a tree already present in the worktree is left alone, and linking twice adds nothing.
5
-
-A filesystem that refuses to make a link is tolerated: the agent still starts.
6
-
-Against a real repo and a real worktree, the linked tree resolves — a dependency file is readable through it, and it is a link rather than a copy.
7
-
- Against real git: the repo's own ignore rule does not cover the links and leaves the worktree dirty, adding the exclusion makes the worktree clean, and adding it a second time does not duplicate the rule.
4
+
- Each tree is mirrored into the worktree at the same relative path as a real directory holding one link per entry, creating any missing parent directory; a tree already present in the worktree is left alone, and mirroring twice adds nothing.
5
+
-The package manager's private state — every dot-entry but the executables directory — is not linked.
6
+
-A filesystem that refuses to make a link, or the directory, is tolerated: the agent still starts.
7
+
- Against a real repo and a real worktree with a pnpm-shaped tree (a package entry that is a relative link into the store), the mirrored tree resolves — a dependency file is readable through the chain, the worktree's directory is real and holds only the package entries — and replacing an entry in the worktree, as an install would, leaves the parent's tree and its link exactly as they were.
0 commit comments