Parallel agents working in different linked worktrees of one registered repository run one at a time, even with free lease slots. This splits Proposal 3 out of #267 so it can be tracked and built on its own. #257 covers the related case of several repositories under one broad root.
Problem
A common agent layout keeps one registered checkout (for example librechat) and gives each task its own linked worktree beneath it: <root>/.worktrees/<task>, created with git worktree add. A coordinating agent fans out subagents, and each works in its own worktree.
The bridge schedules by workspace isolation key, and that key is only the registered root plus an optional conversation instance (workspaceIsolationKey in packages/code/src/protocol.ts). Request cwd and file paths are never part of it. The worker enforces the same key (activeWorkspaceAssignments in worker.ts, per-root execution in native-pool.ts). So every call into any .worktrees/* folder of one repository shares one lane.
Measured on a paired worker with 4 negotiated slots:
| Test |
Wall time |
| Three 15 s commands in three different repositories |
17.5 s (parallel) |
| Two 15 s commands in the same repository |
32.7 s (serialized) |
After #264/#265 and raising lease slots and the exec rate limit, the remaining failures under parallel agents are almost all same-root contention: WORKSPACE_QUEUE_TIMEOUT and COMMAND_UNAVAILABLE, 6% of calls in a busy half hour.
Conversation worktrees (--conversation-worktree-root) don't solve this layout:
- Subagents carry their parent's conversation identity, so they share the parent's checkout and lane.
- Each conversation checkout is a separate full clone, so a coordinator can't see or review the subagents' linked worktrees from its own checkout.
Inferring the lane from cwd is not safe either. The sandbox allows writes to the whole root, so a command in .worktrees/a can cd ../b or edit .git.
Proposal: explicit linked-worktree lanes
-
Protocol. Add an optional worktree field (one path segment) to every workspace-tool request type, including programmatic calls. Workers advertise support with a capability, for example workspaceScopes: ['git_linked_worktree'], and callers only send the field when it is advertised.
-
Scheduling (Code API). Derive the key before admission, for example \0linked-worktree\0<workspaceId>\0<name>, with the root key as its parent. Conflicts are hierarchical:
- a worktree lane conflicts with itself and with its root;
- a root-level request conflicts with the root and with every worktree lane under it.
This touches bridge/store.ts (capability gates, key and parent, admission id), bridge/slots.ts (store the parent per slot and per pending entry in the reservation script), bridge/admission.ts, and programmatic-router.ts.
-
Verification (worker). At admission and under withWorkspaceRoot, before use:
<root>/.worktrees/<name> realpaths to a directory strictly inside the root;
- its
.git is a regular file (checked with lstat) containing gitdir: X, and realpath(X) is <root>/.git/worktrees/<name>;
X/commondir resolves to <root>/.git, and X/gitdir points back to the worktree's .git;
- the worktree's identity is pinned the way roots are (
captureWorkspaceRootIdentity);
- no git process runs during verification, so no hooks or config.
-
Confinement (worker). Re-base cwd and file paths onto the worktree. Narrow the sandbox write set to the worktree plus the shared .git object/ref storage it needs. Deny .git/hooks, .git/config and sibling .git/worktrees/*. Set safeDirectories to the worktree, and disable gc.auto and maintenance.auto for scoped commands.
-
What stays root-scoped: requests without worktree (including a subdirectory cwd), git worktree add/remove/prune, gc, repack, maintenance, and shared-config edits. Creating and removing worktrees is a root operation, so it naturally waits for the lanes under that root.
-
Worker changes: hierarchical waiting and per-scope quarantine in worker.ts, a new linked-worktrees.ts modeled on workspace-instances.ts, sandbox narrowing in native-sandbox.ts, and an opt-in flag and capability in cli.ts.
-
LibreChat (separate PR). When a command cwd or a file path starts with .worktrees/<name>/ and the worker advertises the capability, send worktree: <name> and the re-based path. Agents keep using the same paths. Without the capability, requests are unchanged.
docs/remote-bridge/projects.md already lists the hazards this has to respect: shared Git metadata, overlapping roots, a broad parent running concurrently with its descendants, and shared writable dependency directories.
Acceptance
- Two 15 s commands in
.worktrees/a and .worktrees/b of one root finish in about 15 s with 2+ slots.
- A root-level command waits for both to finish, and worktree requests wait for an in-flight root-level command.
- A scoped command cannot write to a sibling worktree,
.git/config or .git/hooks. A symlinked or forged .worktrees/<name>, or one whose gitdir/commondir don't round-trip, is rejected before dispatch.
- Concurrent commits in two worktrees both succeed (Git ref locking), while
git worktree prune / gc under a scope is denied.
- Callers and workers without the capability behave exactly as today.
Parallel agents working in different linked worktrees of one registered repository run one at a time, even with free lease slots. This splits Proposal 3 out of #267 so it can be tracked and built on its own. #257 covers the related case of several repositories under one broad root.
Problem
A common agent layout keeps one registered checkout (for example
librechat) and gives each task its own linked worktree beneath it:<root>/.worktrees/<task>, created withgit worktree add. A coordinating agent fans out subagents, and each works in its own worktree.The bridge schedules by workspace isolation key, and that key is only the registered root plus an optional conversation instance (
workspaceIsolationKeyinpackages/code/src/protocol.ts). Requestcwdand file paths are never part of it. The worker enforces the same key (activeWorkspaceAssignmentsinworker.ts, per-root execution innative-pool.ts). So every call into any.worktrees/*folder of one repository shares one lane.Measured on a paired worker with 4 negotiated slots:
After #264/#265 and raising lease slots and the exec rate limit, the remaining failures under parallel agents are almost all same-root contention:
WORKSPACE_QUEUE_TIMEOUTandCOMMAND_UNAVAILABLE, 6% of calls in a busy half hour.Conversation worktrees (
--conversation-worktree-root) don't solve this layout:Inferring the lane from
cwdis not safe either. The sandbox allows writes to the whole root, so a command in.worktrees/acancd ../bor edit.git.Proposal: explicit linked-worktree lanes
Protocol. Add an optional
worktreefield (one path segment) to every workspace-tool request type, including programmatic calls. Workers advertise support with a capability, for exampleworkspaceScopes: ['git_linked_worktree'], and callers only send the field when it is advertised.Scheduling (Code API). Derive the key before admission, for example
\0linked-worktree\0<workspaceId>\0<name>, with the root key as its parent. Conflicts are hierarchical:This touches
bridge/store.ts(capability gates, key and parent, admission id),bridge/slots.ts(store the parent per slot and per pending entry in the reservation script),bridge/admission.ts, andprogrammatic-router.ts.Verification (worker). At admission and under
withWorkspaceRoot, before use:<root>/.worktrees/<name>realpaths to a directory strictly inside the root;.gitis a regular file (checked with lstat) containinggitdir: X, and realpath(X) is<root>/.git/worktrees/<name>;X/commondirresolves to<root>/.git, andX/gitdirpoints back to the worktree's.git;captureWorkspaceRootIdentity);Confinement (worker). Re-base
cwdand file paths onto the worktree. Narrow the sandbox write set to the worktree plus the shared.gitobject/ref storage it needs. Deny.git/hooks,.git/configand sibling.git/worktrees/*. SetsafeDirectoriesto the worktree, and disablegc.autoandmaintenance.autofor scoped commands.What stays root-scoped: requests without
worktree(including a subdirectorycwd),git worktree add/remove/prune, gc, repack, maintenance, and shared-config edits. Creating and removing worktrees is a root operation, so it naturally waits for the lanes under that root.Worker changes: hierarchical waiting and per-scope quarantine in
worker.ts, a newlinked-worktrees.tsmodeled onworkspace-instances.ts, sandbox narrowing innative-sandbox.ts, and an opt-in flag and capability incli.ts.LibreChat (separate PR). When a command
cwdor a file path starts with.worktrees/<name>/and the worker advertises the capability, sendworktree: <name>and the re-based path. Agents keep using the same paths. Without the capability, requests are unchanged.docs/remote-bridge/projects.mdalready lists the hazards this has to respect: shared Git metadata, overlapping roots, a broad parent running concurrently with its descendants, and shared writable dependency directories.Acceptance
.worktrees/aand.worktrees/bof one root finish in about 15 s with 2+ slots..git/configor.git/hooks. A symlinked or forged.worktrees/<name>, or one whosegitdir/commondirdon't round-trip, is rejected before dispatch.git worktree prune/gcunder a scope is denied.