Skip to content

Extract kild-git crate and IsolationBackend trait #586

@Wirasm

Description

@Wirasm

Depends on: #587 (Extract kild-git crate)
Blocked until: A second isolation backend is needed (container, VM, etc.)

Problem

Session lifecycle code is hardcoded to git worktrees as the only isolation mechanism. Git calls are spread across `create_session()`, `destroy_session()`, and `complete_session()` with no abstraction boundary — making it impossible to add alternative isolation backends (containers, VMs) without rewriting session lifecycle code.

Location Git operation Severity
`create.rs:113-116` `detect_project()` → `Repository::discover()` Hard — blocks all creation
`create.rs:210-217` `create_worktree()` → `repo.branch()`, `repo.worktree()` Hard — blocks non-`--main` creation
`destroy.rs:308-350` `find_main_repo_root()`, `remove_worktree_*()`, `delete_branch_if_exists()` Hard/Soft mix
`destroy.rs:378-441` `has_remote_configured()`, `get_worktree_status()` Soft — safety checks
`complete.rs:53-174` `kild_branch_name()`, `delete_remote_branch()`, then full destroy Delegates to destroy
`session.rs:21-96` `project_id`, `branch`, `worktree_path`, `use_main_worktree` fields Data model assumption

The codebase already has three proven trait+registry+factory patterns (`TerminalBackend`, `ForgeBackend`, `AgentBackend`) that solve this exact problem for other domains.

Key Design Decision: Worktrees are git, not isolation

Worktrees are git's built-in branch isolation — orthogonal to environment isolation. A container backend would still mount a worktree inside Docker. They compose:

```
kild-git → "what code does the agent see?" (worktree = branch isolation)
IsolationBackend → "what environment does it run in?" (host / container / VM)
session lifecycle → composes both
```

Example flows:

```
Host (current): create worktree → agent runs directly in worktree directory
Container (future): create worktree → docker run -v :/workspace → agent runs in container
VM (future): create worktree → mount worktree in VM → agent runs in VM
No worktree (--main): skip worktree → agent runs in project root (host or container)
```

Prerequisite: #587

The `kild-git` crate extraction (#587) must land first. It:

  • Gives us a clean git API boundary that the isolation trait will compose with
  • Eliminates the 5 direct `git2` leaks outside the git module
  • Makes the isolation trait's composition point obvious: `kild-git` for code isolation, `IsolationBackend` for environment isolation

When to implement this issue

Not now. The `IsolationBackend` trait is premature until there's a second backend to justify the abstraction (YAGNI). With only `HostBackend` (a no-op), the trait adds indirection for zero value.

Trigger: When we're ready to implement container or VM isolation, extract the trait then. The `kild-git` crate boundary (#587) ensures the extraction will be straightforward — the API surface is already the exact seam.

Proposal (for when the time comes)

Trait

```rust
pub trait IsolationBackend: Send + Sync {
fn name(&self) -> &'static str;
fn display_name(&self) -> &'static str;
fn is_available(&self) -> bool;

/// Prepare the execution environment around a working directory.
fn create(&self, request: &IsolationRequest) -> Result<IsolationEnv, IsolationError>;

/// Tear down the execution environment.
fn destroy(&self, env: &IsolationEnv) -> Result<(), IsolationError>;

/// Transform the agent command to run inside the environment.
/// Host backend returns the command unchanged.
/// Container backend wraps it in \`docker exec -it <id>\`.
fn wrap_command(&self, env: &IsolationEnv, command: &str) -> String {
    command.to_string()
}

/// Backend-specific safety warnings before destroy.
fn safety_info(&self, env: &IsolationEnv) -> Result<SafetyInfo, IsolationError> {
    Ok(SafetyInfo::default())
}

}
```

Key types

```rust
pub struct IsolationRequest {
pub working_dir: PathBuf, // worktree path or project root
pub session_name: BranchName,
pub config: IsolationConfig, // backend-specific (image, mounts, etc.)
}

pub struct IsolationEnv {
pub working_dir: PathBuf, // may differ from request (e.g., /workspace in container)
pub backend_name: String,
pub metadata: IsolationMeta,
}

#[non_exhaustive]
pub enum IsolationMeta {
Host,
// Future: Container { id: String, image: String },
// Future: Vm { id: String },
}
```

Session lifecycle flow (after)

```
create_session():

  1. detect_project() ← kild-git
  2. if !use_main: create_worktree() ← kild-git
  3. isolation.create(working_dir) ← IsolationBackend
  4. wrap_command(agent_command) ← IsolationBackend
  5. spawn agent in daemon/terminal

destroy_session():

  1. isolation.destroy() ← IsolationBackend
  2. remove_worktree() ← kild-git
  3. delete_branch() ← kild-git
    ```

Session struct changes

```rust
pub struct Session {
#[serde(alias = "worktree_path")]
pub working_dir: PathBuf,
#[serde(default)]
pub isolation: IsolationMeta,
// project_id and branch stay unchanged
}
```

Config

```toml
[isolation]
default = "host" # only option initially

Future:

[isolation.container]

image = "ubuntu:24.04"

mount_source = true

```

Non-goals

  • Container, VM, devcontainer, or Nix backends (separate issues per backend)
  • Changing project detection to work without git (separate concern)
  • Renaming `BranchName` → `SessionName`

References

Metadata

Metadata

Assignees

Labels

P3Low priority - Nice to have, consider closing if stalearchitectureArchitectural changes and designcore.gitGit worktree operationscore.sessionSession lifecycle (create, destroy, restart, list)effort/highCross-cutting changes, multiple domains, requires design decisionsfeatureNew functionality (planned)

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions