29 KiB
Git Worktree Implementation Plan
Status: Planning Phase Date: 2026-01-12 Author: Generated Implementation Plan
Executive Summary
This document outlines the implementation plan for Git worktrees in Kandev to enable concurrent agent execution on the same repository. Currently, a single repository workspace is mounted into agent containers at /workspace, which prevents multiple agents from working simultaneously on the same codebase without file conflicts.
Current Architecture
Existing Flow
Repository (local_path: /home/user/projects/myapp)
│
▼
Task Creation (repository_url = /home/user/projects/myapp)
│
▼
Orchestrator → Executor → LaunchAgentRequest (RepositoryURL = local_path)
│
▼
Lifecycle Manager → buildContainerConfig()
│
▼
Docker Container with mount:
/home/user/projects/myapp → /workspace (read-write)
Problem Statement
- Multiple agents mounting the same directory causes file conflicts
- Agents may overwrite each other's changes
- No isolation between concurrent agent file modifications
- Merge conflicts are deferred to agent completion
Proposed Architecture
Worktree-Based Isolation
Repository (local_path: /home/user/projects/myapp) [MAIN WORKTREE]
│
├── .git/worktrees/
│ ├── task-abc123/ (worktree metadata, points to ~/.kandev/worktrees/)
│ └── task-def456/ (worktree metadata)
│
▼
~/.kandev/worktrees/ (persistent storage, survives reboots)
├── task-abc123/ (worktree checkout, persists for task lifetime)
│ └── <repo files on branch: kandev/task-abc123>
│
└── task-def456/ (worktree checkout)
└── <repo files on branch: kandev/task-def456>
Each agent container mounts its dedicated worktree instead of the main repository.
1. Worktree Storage Strategy
Directory Structure
~/.kandev/
├── sessions/ # Augment session data
├── config/ # Configuration files
│
└── worktrees/ # Git worktree checkouts
└── {task_id}/ # One worktree per task (task ID is globally unique)
└── <repo files>
Note: Worktrees are keyed by task ID only (not repository ID) because:
- Task IDs are globally unique UUIDs
- Simplifies lookup: given a task, find its worktree directly
- Repository association is tracked in the database
Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Base path | ~/.kandev/worktrees |
Persists across reboots, user-accessible for review |
| Grouping | By task ID only | Task IDs are unique, simplifies lookup |
| Naming | Task ID | Direct 1:1 mapping between task and worktree |
| Permissions | 0755 | Agent containers run as root inside |
| Lifetime | Task lifetime | Worktree exists as long as task exists |
Environment Variable Configuration
# New configuration options (config.yaml or env vars)
KANDEV_WORKTREE_BASE_PATH=~/.kandev/worktrees # Expands to $HOME/.kandev/worktrees
KANDEV_WORKTREE_ENABLED=true
# Note: No cleanup-on-complete or max-age settings - cleanup tied to task deletion
2. Worktree Lifecycle Management
Lifecycle Diagram
flowchart TD
subgraph Lifecycle["WORKTREE LIFECYCLE (Tied to Task Lifetime)"]
TaskCreated["Task Created"] --> AgentLaunch
subgraph AgentLaunch["First Agent Launch"]
Check{"Worktree exists?"}
Check -->|No| Create["Create new worktree & branch"]
Check -->|Yes| Reuse["Reuse existing worktree"]
Create --> Mount["Mount into container"]
Reuse --> Mount
Mount --> Running["Agent Running"]
Running --> Completes["Agent Completes"]
end
Completes --> Persists["Worktree PERSISTS<br/>(changes preserved)"]
Completes --> Followup["User sends follow-up"]
Followup -->|Relaunch| Mount
Persists --> TaskClosed
subgraph TaskClosed["Task Closed/Deleted"]
Options["User chooses:"]
Merge["Merge → merge branch to base, cleanup"]
Discard["Discard → cleanup without merging"]
Keep["Keep Branch → cleanup worktree, keep branch"]
Delete["Task deleted → cleanup worktree and branch"]
end
end
2.1 Creation Timing
Create worktree on FIRST agent launch for a task (if it doesn't exist)
Flow:
- Agent launch requested for task
- Check database: does worktree record exist for this task?
- If YES and path exists on disk → reuse existing worktree
- If YES but path missing → recreate worktree (recovery)
- If NO → create new worktree, store in database
- Mount worktree into container
Reasons:
- Worktree creation requires Git operations that should run on the host
- Failures should prevent container creation rather than fail inside container
- Container mounts must be configured before creation
- Worktree persists for subsequent agent runs on the same task
2.2 Task Resumption (Session Resumption)
When an agent is relaunched for a task (e.g., follow-up prompt):
func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstance, error) {
var workspacePath string
if req.UseWorktree && req.RepositoryPath != "" {
// Check if worktree already exists for this task
existingWT, exists := m.worktreeMgr.GetByTaskID(req.TaskID)
if exists && m.worktreeMgr.IsValid(existingWT.Path) {
// REUSE existing worktree - this is the session resumption case
m.logger.Info("reusing existing worktree for task",
zap.String("task_id", req.TaskID),
zap.String("worktree_path", existingWT.Path),
zap.String("branch", existingWT.Branch))
workspacePath = existingWT.Path
} else if exists && !m.worktreeMgr.IsValid(existingWT.Path) {
// Worktree record exists but directory is missing/corrupted - recreate
m.logger.Warn("worktree directory missing, recreating",
zap.String("task_id", req.TaskID))
wt, err := m.worktreeMgr.Recreate(ctx, req.TaskID)
if err != nil {
return nil, fmt.Errorf("failed to recreate worktree: %w", err)
}
workspacePath = wt.Path
} else {
// No worktree exists - create new one (first run)
wt, err := m.worktreeMgr.Create(ctx, worktree.CreateRequest{
TaskID: req.TaskID,
RepositoryID: req.RepositoryID,
RepositoryPath: req.RepositoryPath,
BaseBranch: req.BaseBranch,
})
if err != nil {
return nil, fmt.Errorf("failed to create worktree: %w", err)
}
workspacePath = wt.Path
}
} else {
workspacePath = req.WorkspacePath
}
// Continue with container creation using workspacePath...
}
Key Insight: The agent will see all changes from previous runs because:
- The worktree directory persists between agent runs
- All file modifications are preserved
- Git status will show uncommitted changes from previous agent work
- The agent can commit, amend, or continue working on existing changes
2.3 Cleanup Strategy (Updated)
| Scenario | Action | Implementation |
|---|---|---|
| Agent completes (success/failure) | Worktree persists | No cleanup action |
| Task cancelled | Worktree persists | No cleanup action |
| User sends follow-up prompt | Reuse worktree | Mount same directory |
| Task closed with "Merge" | Merge branch, then cleanup | worktree.merge action |
| Task closed with "Discard" | Cleanup worktree and branch | worktree.discard action |
| Task deleted | Cleanup worktree and branch | Cascade via task deletion |
| System restart | Reconcile with DB | Keep worktrees for existing tasks |
| Orphaned worktree | Cleanup | Worktree exists but no task in DB |
Critical Change: No automatic cleanup based on time or completion status. Worktrees are only cleaned up when:
- User explicitly closes/deletes the task
- Repository is deleted (cascade)
- Orphan cleanup during reconciliation
2.4 Crash Recovery
On system restart, the lifecycle manager should:
- Scan
~/.kandev/worktrees/for existing directories - Cross-reference with tasks in database
- For each worktree directory:
- If matching task exists → keep worktree (update status if needed)
- If no matching task → cleanup orphaned worktree
- For each worktree record in DB:
- If directory exists → verify it's valid, mark active
- If directory missing → mark for recreation on next agent launch
3. Integration Points
3.1 New Package: internal/agent/worktree/
internal/agent/worktree/
├── manager.go # Worktree lifecycle operations
├── manager_test.go # Unit tests
├── config.go # Configuration
└── errors.go # Error types
3.2 Key Interfaces
// internal/agent/worktree/manager.go
// WorktreeManager handles Git worktree operations
type WorktreeManager interface {
// Create creates a new worktree for a task
Create(ctx context.Context, req CreateRequest) (*Worktree, error)
// Get returns worktree info for a task
Get(taskID string) (*Worktree, bool)
// Remove removes a worktree and optionally its branch
Remove(ctx context.Context, taskID string, removeBranch bool) error
// Cleanup removes all worktrees older than maxAge
Cleanup(ctx context.Context, maxAge time.Duration) error
// Reconcile syncs worktree state with active tasks
Reconcile(ctx context.Context, activeTasks []string) error
}
// CreateRequest contains parameters for worktree creation
type CreateRequest struct {
TaskID string
RepositoryID string
RepositoryPath string // Main repository local path
BaseBranch string // Branch to base worktree on
BranchName string // New branch name for worktree (optional)
}
// Worktree represents a git worktree
type Worktree struct {
ID string // Unique worktree ID
TaskID string // Associated task (1:1 relationship)
RepositoryID string // Associated repository
Path string // Absolute path to worktree directory
Branch string // Branch name in the worktree
BaseBranch string // Branch it was created from
Status string // active, merged, deleted
CreatedAt time.Time
UpdatedAt time.Time
}
// Additional methods for task resumption
type WorktreeManager interface {
// ... existing methods ...
// GetByTaskID returns the worktree for a specific task (if exists)
GetByTaskID(taskID string) (*Worktree, bool)
// IsValid checks if a worktree directory is valid and usable
IsValid(path string) bool
// Recreate recreates a worktree from stored metadata
Recreate(ctx context.Context, taskID string) (*Worktree, error)
// OnTaskDeleted cleans up worktree when task is deleted
OnTaskDeleted(ctx context.Context, taskID string) error
}
3.3 Modified Files
internal/agent/lifecycle/manager.go
// Current LaunchRequest
type LaunchRequest struct {
TaskID string
AgentType string
WorkspacePath string // Currently: direct mount path
TaskDescription string
Env map[string]string
Metadata map[string]interface{}
}
// Enhanced LaunchRequest (add new fields)
type LaunchRequest struct {
TaskID string
AgentType string
WorkspacePath string // CHANGED: Will be worktree path
RepositoryID string // NEW: For worktree grouping
RepositoryPath string // NEW: Original repo path
BaseBranch string // NEW: Branch to base worktree on
UseWorktree bool // NEW: Enable worktree mode
TaskDescription string
Env map[string]string
Metadata map[string]interface{}
}
internal/agent/lifecycle/manager.go - Launch Method Changes
func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstance, error) {
// ... existing validation ...
// NEW: Create worktree if enabled
var workspacePath string
if req.UseWorktree && req.RepositoryPath != "" {
wt, err := m.worktreeMgr.Create(ctx, worktree.CreateRequest{
TaskID: req.TaskID,
RepositoryID: req.RepositoryID,
RepositoryPath: req.RepositoryPath,
BaseBranch: req.BaseBranch,
})
if err != nil {
return nil, fmt.Errorf("failed to create worktree: %w", err)
}
workspacePath = wt.Path
// Store worktree info in instance metadata
if req.Metadata == nil {
req.Metadata = make(map[string]interface{})
}
req.Metadata["worktree_path"] = wt.Path
req.Metadata["worktree_branch"] = wt.Branch
} else {
workspacePath = req.WorkspacePath
}
// Update req.WorkspacePath for container config
req.WorkspacePath = workspacePath
// ... rest of existing Launch logic ...
}
3.4 Orchestrator Changes
internal/orchestrator/executor/executor.go
// Update LaunchAgentRequest
type LaunchAgentRequest struct {
TaskID string
AgentType string
RepositoryURL string // Rename to RepositoryPath for clarity
RepositoryID string // NEW
Branch string
UseWorktree bool // NEW: defaults to true
TaskDescription string
Priority int
Metadata map[string]interface{}
}
4. Branch Management
4.1 Branch Naming Convention
kandev/{task_id}
Examples:
- kandev/abc123def456
- kandev/789xyz012345
Benefits:
- Unique per task (no conflicts)
- Easy to identify Kandev-managed branches
- Simple cleanup (delete branches matching
kandev/*)
4.2 Branch Creation Strategy
func (m *Manager) createWorktreeBranch(repoPath, taskID, baseBranch string) (string, error) {
branchName := fmt.Sprintf("kandev/%s", taskID)
worktreePath := filepath.Join(m.config.BasePath, taskID)
// Create worktree with new branch from base
cmd := exec.Command("git", "worktree", "add",
"-b", branchName, // Create new branch
worktreePath, // Worktree path
baseBranch) // Base branch (e.g., "main")
cmd.Dir = repoPath
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("git worktree add failed: %w", err)
}
return branchName, nil
}
4.3 Conflict Prevention
Since each task gets a unique branch name based on task ID, there are no naming conflicts between concurrent agents.
4.4 Merge Strategy
After agent completion, the user can:
- Auto-merge: Merge worktree branch to base branch (if no conflicts)
- Manual review: Keep worktree for manual review and merge
- Discard: Delete worktree and branch without merging
// MergeOptions for completing a worktree
type MergeOptions struct {
TaskID string
MergeMethod string // "merge", "rebase", "squash"
AutoCleanup bool // Delete worktree after merge
}
func (m *Manager) MergeAndCleanup(ctx context.Context, opts MergeOptions) error {
wt, ok := m.Get(opts.TaskID)
if !ok {
return fmt.Errorf("worktree not found for task: %s", opts.TaskID)
}
// Switch to base branch in main repo
// Merge/rebase the worktree branch
// Delete worktree if AutoCleanup
return nil
}
5. Concurrency Considerations
5.1 Race Condition Prevention
Problem: Multiple agents creating worktrees simultaneously for the same repository.
Solution: Repository-level mutex lock
type Manager struct {
config Config
logger *logger.Logger
worktrees map[string]*Worktree // taskID -> worktree
mu sync.RWMutex // Protects worktrees map
repoLocks map[string]*sync.Mutex // repoPath -> mutex
repoLockMu sync.Mutex // Protects repoLocks map
}
func (m *Manager) getRepoLock(repoPath string) *sync.Mutex {
m.repoLockMu.Lock()
defer m.repoLockMu.Unlock()
if lock, exists := m.repoLocks[repoPath]; exists {
return lock
}
lock := &sync.Mutex{}
m.repoLocks[repoPath] = lock
return lock
}
func (m *Manager) Create(ctx context.Context, req CreateRequest) (*Worktree, error) {
// Get repository-specific lock
repoLock := m.getRepoLock(req.RepositoryPath)
repoLock.Lock()
defer repoLock.Unlock()
// Safe to create worktree now
// ...
}
5.2 Git Object Sharing
Git worktrees share the object database (.git/objects/) with the main repository. This is safe because:
- Git uses content-addressable storage (SHA hashes)
- Objects are immutable once created
- Git handles concurrent access internally
5.3 Worktree Limits
const MaxWorktreesPerRepo = 10
func (m *Manager) Create(ctx context.Context, req CreateRequest) (*Worktree, error) {
// Check current worktree count for repository
count := m.countWorktreesForRepo(req.RepositoryID)
if count >= MaxWorktreesPerRepo {
return nil, fmt.Errorf("maximum worktrees (%d) reached for repository", MaxWorktreesPerRepo)
}
// ...
}
6. Error Handling
6.1 Error Types
// internal/agent/worktree/errors.go
var (
ErrWorktreeExists = errors.New("worktree already exists for task")
ErrWorktreeNotFound = errors.New("worktree not found")
ErrRepoNotGit = errors.New("repository is not a git repository")
ErrBranchExists = errors.New("branch already exists")
ErrMaxWorktrees = errors.New("maximum worktrees reached for repository")
ErrWorktreeLocked = errors.New("worktree is locked by another process")
ErrInvalidBaseBranch = errors.New("base branch does not exist")
)
6.2 Worktree Creation Failure
func (m *Manager) Create(ctx context.Context, req CreateRequest) (*Worktree, error) {
// Validate repository is a git repo
if !m.isGitRepo(req.RepositoryPath) {
return nil, ErrRepoNotGit
}
// Validate base branch exists
if !m.branchExists(req.RepositoryPath, req.BaseBranch) {
return nil, fmt.Errorf("%w: %s", ErrInvalidBaseBranch, req.BaseBranch)
}
// Attempt worktree creation
wt, err := m.createWorktree(ctx, req)
if err != nil {
// Cleanup any partial state
m.cleanupPartialWorktree(req.TaskID)
return nil, err
}
return wt, nil
}
6.3 Corrupted Worktree Recovery
func (m *Manager) Repair(ctx context.Context, taskID string) error {
wt, ok := m.Get(taskID)
if !ok {
return ErrWorktreeNotFound
}
// Check if worktree is corrupted
if !m.isWorktreeValid(wt.Path) {
// Remove corrupted worktree
m.Remove(ctx, taskID, false)
// Recreate from stored metadata
// (requires storing CreateRequest in worktree record)
return m.recreateWorktree(ctx, taskID)
}
return nil
}
func (m *Manager) isWorktreeValid(path string) bool {
// Check .git file exists and points to valid location
gitFile := filepath.Join(path, ".git")
content, err := os.ReadFile(gitFile)
if err != nil {
return false
}
// Parse gitdir: path and verify it exists
// ...
return true
}
6.4 Fallback for Non-Git Repositories
If a repository doesn't support worktrees (e.g., not a Git repo), fall back to current behavior:
func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstance, error) {
if req.UseWorktree && req.RepositoryPath != "" {
if !m.worktreeMgr.IsGitRepo(req.RepositoryPath) {
m.logger.Warn("repository is not a git repo, falling back to direct mount",
zap.String("path", req.RepositoryPath))
req.UseWorktree = false
}
}
// Continue with either worktree or direct mount...
}
7. Database Schema Changes
7.1 New Table: worktrees
CREATE TABLE IF NOT EXISTS worktrees (
id TEXT PRIMARY KEY,
task_id TEXT NOT NULL UNIQUE, -- 1:1 relationship with task
repository_id TEXT NOT NULL,
repository_path TEXT NOT NULL, -- Store for recreation
path TEXT NOT NULL, -- Worktree directory path
branch TEXT NOT NULL, -- Branch name (kandev/{task_id})
base_branch TEXT NOT NULL, -- Original branch it was based on
status TEXT NOT NULL DEFAULT 'active', -- active, merged, deleted
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
merged_at DATETIME,
deleted_at DATETIME,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (repository_id) REFERENCES repositories(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_worktrees_task_id ON worktrees(task_id);
CREATE INDEX IF NOT EXISTS idx_worktrees_repository_id ON worktrees(repository_id);
CREATE INDEX IF NOT EXISTS idx_worktrees_status ON worktrees(status);
Key Points:
task_id UNIQUEensures 1:1 relationship between task and worktreeON DELETE CASCADEautomatically cleans up worktree record when task is deletedrepository_pathstored for worktree recreation if directory is lostupdated_attracks last access/modification
7.2 Task Deletion Hook
When a task is deleted, the worktree cleanup is triggered:
// internal/task/service/service.go
func (s *Service) DeleteTask(ctx context.Context, taskID string) error {
// Get worktree info before deleting task
wt, exists := s.worktreeMgr.GetByTaskID(taskID)
// Delete task (cascades to worktrees table)
if err := s.repo.DeleteTask(ctx, taskID); err != nil {
return err
}
// Clean up worktree directory and git branch
if exists {
if err := s.worktreeMgr.Cleanup(ctx, wt); err != nil {
s.logger.Warn("failed to cleanup worktree directory",
zap.String("task_id", taskID),
zap.Error(err))
// Don't fail the deletion - worktree cleanup is best-effort
}
}
return nil
}
7.3 Task Metadata Enhancement
Store worktree information in task metadata for API access:
// After worktree creation, update task metadata
task.Metadata["worktree"] = map[string]interface{}{
"id": wt.ID,
"path": wt.Path,
"branch": wt.Branch,
"base_branch": wt.BaseBranch,
"status": wt.Status,
"created_at": wt.CreatedAt.Format(time.RFC3339),
}
7.4 Close Task API
New API for closing a task with merge options:
// CloseTaskRequest represents options for closing a task
type CloseTaskRequest struct {
TaskID string `json:"task_id"`
MergeOption string `json:"merge_option"` // "merge", "squash", "discard", "keep_branch"
}
// CloseTask closes a task and handles worktree according to merge option
func (s *Service) CloseTask(ctx context.Context, req CloseTaskRequest) error {
wt, exists := s.worktreeMgr.GetByTaskID(req.TaskID)
if !exists {
// No worktree - just close the task
return s.repo.UpdateTaskStatus(ctx, req.TaskID, "closed")
}
switch req.MergeOption {
case "merge", "squash":
if err := s.worktreeMgr.MergeToBase(ctx, wt, req.MergeOption); err != nil {
return fmt.Errorf("failed to merge: %w", err)
}
fallthrough
case "discard":
if err := s.worktreeMgr.Cleanup(ctx, wt); err != nil {
return fmt.Errorf("failed to cleanup: %w", err)
}
case "keep_branch":
// Delete worktree directory but keep the branch for manual merge
if err := s.worktreeMgr.RemoveWorktreeOnly(ctx, wt); err != nil {
return fmt.Errorf("failed to remove worktree: %w", err)
}
}
return s.repo.UpdateTaskStatus(ctx, req.TaskID, "closed")
}
8. Implementation Steps
Phase 1: Core Worktree Package (2-3 days)
- Create
internal/agent/worktree/package - Implement
Managerwith Create, Remove, Get operations - Add repository-level locking
- Write unit tests with mock git operations
Phase 2: Lifecycle Integration (2 days)
- Add
WorktreeManagerto lifecycleManager - Modify
LaunchRequestto include worktree options - Update
Launch()to create worktree before container - Update
StopAgent()to handle worktree cleanup - Update container labels to track worktree path
Phase 3: Orchestrator Integration (1-2 days)
- Update
LaunchAgentRequestin executor - Pass repository information through the chain
- Add configuration for worktree mode (enabled/disabled)
Phase 4: Cleanup & Recovery (1-2 days)
- Implement periodic cleanup job
- Add reconciliation on startup
- Handle crash recovery scenarios
Phase 5: Branch Management API (2 days)
- Add merge/discard endpoints
- Implement WebSocket actions for worktree management
- Add frontend support for merge decisions
Phase 6: Testing & Documentation (1-2 days)
- Integration tests with real Git operations
- Performance testing with concurrent agents
- Update ARCHITECTURE.md and AGENTS.md
9. Configuration
9.1 New Configuration Options
// internal/common/config/config.go
type WorktreeConfig struct {
Enabled bool `yaml:"enabled" env:"KANDEV_WORKTREE_ENABLED" default:"true"`
BasePath string `yaml:"base_path" env:"KANDEV_WORKTREE_BASE_PATH" default:"~/.kandev/worktrees"`
MaxPerRepo int `yaml:"max_per_repo" env:"KANDEV_WORKTREE_MAX_PER_REPO" default:"10"`
BranchPrefix string `yaml:"branch_prefix" env:"KANDEV_WORKTREE_BRANCH_PREFIX" default:"kandev/"`
}
// Note: No cleanup_on_complete or max_age - cleanup is tied to task deletion
9.2 Example Configuration
# config.yaml
worktree:
enabled: true
base_path: ~/.kandev/worktrees # Expands $HOME at runtime
max_per_repo: 10
branch_prefix: kandev/
10. API Changes
10.1 New WebSocket Actions
// Worktree management actions
"worktree.list" // List worktrees for a repository
"worktree.get" // Get worktree details for a task
"worktree.merge" // Merge worktree branch to base
"worktree.discard" // Delete worktree and branch
"worktree.diff" // Get diff between worktree and base
10.2 Example Messages
List Worktrees:
{
"id": "uuid",
"type": "request",
"action": "worktree.list",
"payload": {
"repository_id": "repo-uuid"
}
}
Merge Worktree:
{
"id": "uuid",
"type": "request",
"action": "worktree.merge",
"payload": {
"task_id": "task-uuid",
"merge_method": "squash",
"cleanup_after": true
}
}
11. Testing Strategy
Unit Tests
- Worktree creation/removal
- Locking mechanisms
- Error handling
- Configuration parsing
Integration Tests
- Real Git operations with test repositories
- Concurrent worktree creation
- Cleanup scenarios
- Recovery from crashes
E2E Tests
- Full task execution with worktree
- Multiple concurrent agents on same repo
- Merge workflow
12. Rollback Plan
If issues arise with worktrees:
- Set
KANDEV_WORKTREE_ENABLED=falsein configuration - System falls back to direct mount mode
- Existing worktrees can be cleaned up manually
- No data loss - main repository remains unchanged
Appendix A: Git Worktree Commands Reference
# Create worktree with new branch
git worktree add -b kandev/task-123 /tmp/kandev/worktrees/task-123 main
# List worktrees
git worktree list
# Remove worktree
git worktree remove /tmp/kandev/worktrees/task-123
# Prune stale worktree entries (after manual deletion)
git worktree prune
# Lock worktree (prevent garbage collection)
git worktree lock /tmp/kandev/worktrees/task-123
# Unlock worktree
git worktree unlock /tmp/kandev/worktrees/task-123
Appendix B: Sequence Diagram - Agent Launch with Worktree
sequenceDiagram
participant Client
participant Orchestrator
participant Executor
participant LifecycleManager
participant WorktreeManager
participant Docker
Client->>Orchestrator: orchestrator.start
Orchestrator->>Executor: ExecuteTask
Executor->>LifecycleManager: LaunchAgent
LifecycleManager->>WorktreeManager: Create()
WorktreeManager->>Docker: git worktree add
Docker-->>WorktreeManager: done
WorktreeManager-->>LifecycleManager: worktree.Path
LifecycleManager->>Docker: CreateContainer (mount worktree)
Docker-->>LifecycleManager: container created
LifecycleManager-->>Executor: agent launched
Executor-->>Orchestrator: task executing
Orchestrator-->>Client: response