Skip to content

Schedule linked worktrees under one registered root as separate lanes #268

Description

@danny-avila

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

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

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

  3. 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.
  4. 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.

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

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions