Skip to content

Isolate opencode-skein's own agent execution in per-change git worktrees #19

Description

@androidand
Proposal

Why

Two or more opencode-skein agents (manually launched TUI instances, or /loop
sessions) working in the same checkout will happily switch branches out from
under each other — there is no isolation between concurrent git operations in
one working directory. This is a live, recurring pain, not a hypothetical: the
2026-07-26 incident that motivated session-summary-write-amplification also
surfaced two opencode-skein processes running concurrently against the same
/Users/andreas/dev/brick-now checkout
, with no separation between them.

This is not a new problem for the ecosystem — it already has a working
solution one level up. The external skein orchestrator creates a dedicated
git worktree per change before dispatching a coder agent, confirmed directly
from this repo's own change handoffs:

  • openspec/changes/error-boundaries-plan/.skein/coder-context.md:
    Repo root: /Users/andreas/dev/opencode-worktrees/error-boundaries-plan
  • openspec/changes/refactor-context-management-to-intr/.skein/coder-context.md:
    Repo root: /Users/andreas/dev/opencode-worktrees/refactor-context-management-to-intr

opencode-skein's own /loop and Task-tool build agents have no equivalent.
This gap has already been named twice in the backlog and punted both times:

  • loop-spec-queue's Non-Goals: "No parallel execution of changes. Serial
    only; parallel edits to one working tree need worktree isolation, which
    is a separate change
    ."
  • provider-slot-leases's Non-Goals: "Not leasing anything other than
    provider slots — file locks and repo/worktree contention between
    instances are a separate problem
    ."

This change is that separate change.

What Changes

  1. A worktree lifecycle module, packages/opencode/src/git/worktree.ts,
    following this repo's existing precedent for shelling out to git
    (packages/opencode/src/snapshot/index.ts's git() helper over
    ChildProcessSpawner; script/sync-upstream.ts's
    git worktree add -b <branch> <path> <base> invocation):
    • ensure(slug) — reuse ../opencode-worktrees/<slug> if it already
      exists (from a previous run, or from skein itself — same convention,
      same path, so the two systems never collide); otherwise
      git worktree add -b <branch> <path> <base>.
    • merge(slug) — from the main checkout, git merge --no-ff the
      worktree's branch locally. Never pushes — matches loop-spec-queue's
      already-decided authority boundary (edit/test/verify/commit locally,
      stop before push).
    • cleanup(slug) — git worktree remove, only after a successful merge.
  2. Wired into /loop's start path, behind an experimental flag
    (experimental.agent_worktree_isolation, default off — this changes
    where an agent writes files and which branch it operates on, so it stays
    opt-in until proven safe, unlike local_subagent_placement's default-on
    opt-out convention).
  3. Sibling-directory convention documented: ../opencode-worktrees/<slug>,
    matching skein's existing layout exactly, so a human or either tool can
    find and reuse the same worktree.

Non-Goals

  • No "pick the next spec automatically" behavior. That is
    loop-spec-queue's job, once it exists — this change only makes worktree
    isolation available to whatever drives a loop into a change. It does not
    itself decide which change to work on next or call specsync.
  • No blocking on loop-spec-queue. That change has zero implemented
    tasks and is itself gated behind three other unimplemented changes. This
    change is usable standalone: point a loop at one change, it gets a
    worktree.
  • No Task-tool subagent worktrees. Subagents dispatched via the Task tool
    are mostly read-heavy or make small, short-lived edits; the collision risk
    observed so far is between full /loop/build sessions. Revisit only if
    subagents are observed causing the same problem.
  • No cross-host coordination. This is single-machine, single-checkout
    isolation. Fleet-wide coordination is fleet-instance-presence /
    agent-coordination-bus's domain.
  • No file-level locking across processes, and no attempt to prevent two
    different opencode-skein instances from independently choosing the same
    change — ensure() reusing an existing worktree by path handles the common
    case (same slug → same directory) but is not a distributed lock.

Impact

  • New: packages/opencode/src/git/worktree.ts.
  • Modified: packages/opencode/src/loop/* (start/stop path, behind the flag).
  • Config: new experimental.agent_worktree_isolation boolean.
  • No effect on any existing behavior when the flag is off (the default).

Disposition (2026-09-18)

Archived — superseded/obsolete. Dropped 2026-09-18: src/git/worktree.ts was never imported; upstream's Worktree service + TUI move-session-to-worktree covers per-session isolation. Queue-mode auto-isolation + merge-back can be a new change on upstream's service.

Tasks

Phase 1: Worktree lifecycle module (this session's implementation target)

  • 1.1 packages/opencode/src/git/worktree.ts: implement ensure(repoRoot, slug, base?) —
    compute ../opencode-worktrees/<slug> relative to repoRoot; if it
    exists and is a valid git worktree, return its path; otherwise run
    git worktree add -b <branch> <path> <base> (branch name:
    loop/<slug>, matching loop-spec-queue task 4.4's convention) and
    return the new path. Verify: unit test against a scratch git repo —
    calling twice with the same slug returns the same path and does not
    error the second time.
    • Signature takes repoRoot explicitly rather than reading it from
      instance state, so Phase 1 is testable standalone without pulling in the
      Effect service/InstanceState machinery. Phase 2 wires it to the real
      repo root when integrating with /loop.
  • 1.2 Implement merge(slug) — from the main checkout, git merge --no-ff
    the worktree's branch. Never runs git push. Verify: unit test — after
    a commit in the worktree, merge brings that commit into the main
    checkout's current branch; the main checkout's branch is unchanged if
    the worktree has no commits ahead.
  • 1.3 Implement cleanup(slug) — git worktree remove the path. Only
    call after a successful merge. Verify: unit test — the worktree
    directory and its git metadata are gone after cleanup; a second
    cleanup on an already-removed slug does not throw.
  • 1.4 Error handling: ensure on a path that exists but is not a valid
    git worktree (e.g. a stray directory) fails with a clear error rather
    than silently reusing it or corrupting it. Verify: unit test.
  • 1.5 Full typecheck (bun run --cwd packages/opencode typecheck) and the
    new unit test suite green.
    • packages/opencode/test/git/worktree.test.ts — 7 tests, all passing,
      against real scratch git repos (init, commit, worktree add/merge/remove).

Phase 2: Wire into /loop (follow-up, not this session)

  • 2.1 Add experimental.agent_worktree_isolation: Schema.optional(Schema.Boolean)
    to packages/core/src/v1/config/config.ts, default off (only enabled
    when explicitly true, opposite polarity from local_subagent_placement).
  • 2.2 On loop start with the flag on: resolve the target change's slug,
    call ensure(slug), and run the loop's work rooted at that worktree
    path instead of the main checkout.
  • 2.3 On loop completion (success): call merge(slug) then
    cleanup(slug). On halt/failure: leave the worktree in place (matches
    loop-spec-queue's "leave the working tree as-is" halt semantics) and
    report its path so the user can inspect or resume it manually.
  • 2.4 Manual end-to-end: start a loop on change A with the flag on,
    confirm it runs in ../opencode-worktrees/A and the main checkout is
    untouched; start a second loop on change B concurrently, confirm it
    gets its own worktree and neither loop's git operations affect the
    other's branch.
  • 2.5 Manual: confirm a skein-created worktree for the same slug is
    reused rather than duplicated (same path convention).

Phase 3: Documentation

  • 3.1 Document the ../opencode-worktrees/<slug> convention and the flag
    in whatever surfaces /loop's other experimental flags today (CLI
    help, --help output, or the relevant docs file — match however
    local_subagent_placement is documented, if at all).

Unchecked items above: see Disposition in proposal.md (2026-09-18).

Plan changes

5 done

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions