workspace-isolation

module
v0.0.0-...-383fb9b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT

README

wi — workspace isolation

Deterministic, multi-repo workspace isolation for parallel AI coding agents.

wi lets many agents (or many humans) work on the same set of repositories at the same time without stepping on each other. Each task gets its own set of isolated git worktrees carved from a pristine local mirror; when the work is done, wi land fast-forwards it back onto the base branch. Every command speaks one stable JSON envelope and a fixed exit-code contract, so an agent can drive wi without ever parsing prose.

It is a small, sharp primitive, not a framework: no LLM dependency, no hidden network, no daemon. One static Go binary, one runtime dependency (a JSON-schema validator). The agent is the brain; wi is the deterministic hands.


Why

Running several agents against one repo means merge chaos, half-applied changes, and "who moved my branch?". The usual fixes — one clone per agent, or git worktree by hand — drift out of sync and leave orphaned debris nobody can safely clean up.

wi makes isolation a first-class, crash-safe, self-healing operation:

  • Single Source Of Truth (SSOT) mirror — one local reference clone per repo, kept detached at the base tip. Isolates are cut from it; the SSOT itself is never dirtied and never grows stray branches.
  • Isolated worktreeswi isolate new <task> materializes one worktree per repo under isolas/<task>/<repo>/. The agent works there, fully isolated.
  • Fast-forward-only landingwi land advances the base ref only by fast-forward; it never force-pushes, never reset --hards, and parks a durable, resumable record if a repo can't land cleanly.
  • Evidence-positive reclamationwi gc only deletes what it can prove it owns (via refs/wi/owned/* marker refs). An unexplained orphan is a loud refusal, never a silent delete.
  • Self-healing — durable op-journal + offline roll-forward recovery, liveness-aware locks (pid/host/boot_id), and three-way drift repair mean a crashed agent leaves a workspace wi can put back together.

Install

wi is a single, dependency-free binary.

One command — handles a missing or outdated Go
git clone https://github.com/ggkguelensan/workspace-isolation.git
cd workspace-isolation
./install.sh

install.sh is a portable POSIX script (plain sh, no bashisms). It detects your OS/arch, looks for a published release binary, and falls back to go install automatically when none matches. If Go is missing or older than 1.26 it prints exactly how to install or upgrade it — it never installs an unverified or half-built binary. Useful knobs:

./install.sh --dry-run                        # show every step; change nothing
WI_INSTALL_DIR=/usr/local/bin ./install.sh    # pick the dir (default: ~/.local/bin)

Once the script lands on the default branch you can also run it without cloning first:

curl -fsSL https://raw.githubusercontent.com/ggkguelensan/workspace-isolation/main/install.sh | sh
Manual — you already have Go 1.26+
git clone https://github.com/ggkguelensan/workspace-isolation.git
cd workspace-isolation
go install ./cmd/wi          # → $(go env GOPATH)/bin/wi   (usually ~/go/bin/wi)

If the install dir (~/.local/bin for the script, ~/go/bin for go install) isn't on your PATH yet:

echo 'export PATH="$HOME/.local/bin:$(go env GOPATH)/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc

Verify:

wi help        # prints the command catalog as a JSON envelope

No prebuilt release is published yet — .goreleaser.yaml and a Homebrew tap are scaffolded for tagged releases, and install.sh will prefer a release binary the moment one exists. Until then it builds from source via go install.


Quick start (the happy path)

# 1. Bootstrap a workspace in the current directory (creates .wi/ + wi.config.jsonc)
wi init

# 2. Declare the repos you want to work across
wi repo add api  https://github.com/you/api.git  --base main
wi repo add web  https://github.com/you/web.git  --base main

# 3. Fetch them into the local SSOT mirror
wi sync

# 4. Cut an isolated worktree set for a task (one or more repos)
wi isolate new add-checkout api web

# 5. Find out where to work, then work there
wi resolve add-checkout        # → path bundle: isolas/add-checkout/api, .../web
#    ... edit, commit inside those worktrees as usual ...

# 6. Land the work back onto each repo's base branch (fast-forward only)
wi land add-checkout api web

# 7. Tear down the isolate when you're done
wi isolate rm add-checkout

If a land can't fast-forward cleanly it parks instead of failing destructively:

wi land status   add-checkout   # per-repo phase: landed / blocked / pending
# ... rebase the blocked worktree onto the new base, then ...
wi land continue add-checkout   # re-attempt the blocked repos
# ... or, to undo everything that did land and rewind to the pre-land state:
wi land abort    add-checkout

Add --dry-run to any mutating command to see the plan with zero side effects (always exits 0). Add --format text for a human-readable projection of the same envelope.


How it works

<root>/
  wi.config.jsonc          # committed manifest: which repos, which base branch
  repos/<repo>/            # SSOT reference clone, detached at base tip (never dirtied)
  isolas/<task>/<repo>/    # per-task isolated worktrees — where agents actually work
  .wi/                     # machine state (git-ignored)
    state/                 # per-task isolate registry
    land/                  # durable parked-land records (resume after a block/crash)
    journal/               # append-only op journal → offline roll-forward recovery
    locks/                 # liveness-aware advisory locks (pid/host/boot_id)
    mirrors/               # cached fetch-freshness snapshots (no network on read)
    log/                   # one JSONL trace per op_id

The manifest (wi.config.jsonc) is JSON with // comments:

{
  "defaults": { "base": "main" },     // inherited by repos that omit their own base
  "repos": [
    { "name": "api", "url": "https://github.com/you/api.git" },
    { "name": "web", "url": "https://github.com/you/web.git", "base": "develop" }
  ]
}

Command reference

Command What it does
wi init Create the .wi/ skeleton + wi.config.jsonc in the current directory
wi repo add <name> <url> [--base <branch>] Register a source repository in the manifest
wi sync [<repo>…] Fetch declared repos into their local SSOT mirror (all if none named)
wi isolate new <task> <repo>… Materialize an isolated worktree set for a task
wi resolve <task> Print the path bundle for a task's isolate
wi land <task> <repo>… [--atomic] Fast-forward each repo's isolate work onto its base branch
wi land status <task> Show a parked land's per-repo phase (landed / blocked / pending)
wi land continue <task> Resume a parked land, re-attempting each blocked repo
wi land abort <task> Undo a parked or completed land, rewinding each base to pre-land state
wi isolate rm <task> [<repo>…] Remove a task's isolate and release its worktrees
wi isolate repair <task> Reconcile an isolate with its on-disk worktrees + ownership markers
wi gc [--dry-run] Reclaim leftover worktrees wi can prove it owns
wi state cas <ns> <key> --expected <v|__ABSENT__> --new <v> Atomic compare-and-swap on a namespaced key (agent coordination)
wi lock ls List workspace locks and whether each is safe to break (unix)
wi lock break <key> Displace a stale lock — only when its holder is proven dead (unix)
wi help [topic] Self-describing command catalog (great for agents)

Global flags (accepted in any position): --dry-run, --format {json\|text}.


The machine contract

Every command — success or failure — prints exactly one JSON envelope to stdout:

{
  "schema_version": "1.2",
  "capabilities": ["help-json","resolve-block","dry-run","partial-success","land","land-atomic","state-kv"],
  "op_id": "op_mr0ktqkr_iok725gp",
  "command": "isolate new",
  "ok": true,
  "action": "created",
  "dry_run": false,
  "repos": [{ "repo": "api", "action": "created" }],
  "warnings": [],
  "next": ["wi resolve add-checkout"],
  "error": null
}

On failure, ok is false and error carries a closed kind plus a stable sub-code, a human message, and runnable did_you_mean / help hints. Agents branch on error.kind and the exit code — never on message text.

Exit codes
Code Meaning
0 success — includes every --dry-run, help, and noop
2 durable, resumable partial success (some repos done, one parked)
3 not_found — unknown repo / task / help topic
4 conflict / dirty_worktree / already_exists — refused before any write
5 needs_approval — reserved for hooks/approvals (v2)
6 lock_held (transient contention), or mirror_stale on the land path
64 usage — bad args, unknown flag, malformed manifest
70 internal — infrastructure/I-O failure
130 interrupted (SIGINT)

The full enum/exit/action vocabulary is frozen in internal/contract and guarded by contract.lock.json + TestContractFrozen.


Using wi as a Claude Code skill

wi ships with a Claude Code skill so an agent automatically isolates a workspace when it starts a new task instead of editing repos in place. See skill/SKILL.md for the source and skill/INSTALL.md for the two-step install (copy the skill into ~/.claude/skills/wi/ and register a /wi line in your global CLAUDE.md). Once installed, "isolate this work" / "start a new task in parallel" triggers the full wi isolate new … → work → wi land flow, with the agent parsing the JSON envelope at every step.


Design & invariants

wi holds a short list of non-negotiable invariants (see DESIGN.md §2): no LLM dependency, no hidden network, SSOT stays pristine (detached-HEAD + update-ref only), base mutation is fast-forward-only, reclamation is evidence-positive, and every command emits exactly one envelope. The build is fitness-test-first: every guard ships a paired non-vacuity mutant, and the full closed-enum contract is drift-tested in CI.

The architecture and rationale live in DESIGN.md; the milestone build plan lives in IMPLEMENTATION_PLAN.md.


Status

  • MVP (M0–M3): complete and green — the full init → repo add → sync → isolate new → resolve → isolate rm pipeline, the frozen wire contract, and CI (linux + macOS).
  • v1 (M4): in progressland / land continue|abort|status, gc, liveness-aware locks, durable journal + crash recovery, and isolate repair are all in place. The remaining M4 item is wi doctor / wi check (read-only health diagnosis with a bounded --fix).
  • M5 (agent ergonomics: step/ports/hooks) is deferred past v1.

License

MIT © 2026 ggkguelensan

Directories

Path Synopsis
cmd
wi command
Command wi is the workspace-isolation CLI entry point: the SINGLE main package and the SINGLE os.Exit site in the tree (via exitcontract.Exit, DESIGN §4).
Command wi is the workspace-isolation CLI entry point: the SINGLE main package and the SINGLE os.Exit site in the tree (via exitcontract.Exit, DESIGN §4).
internal
baseref
Package baseref is the seam that turns a base declared as an ordered candidate list (defaults.base / repo.base = ["dev","main"] = "prefer dev, else main", DESIGN §1) into the single effective base branch the domain cores consume.
Package baseref is the seam that turns a base declared as an ordered candidate list (defaults.base / repo.base = ["dev","main"] = "prefer dev, else main", DESIGN §1) into the single effective base branch the domain cores consume.
cli
Package cli is wi's uniform command pipeline (DESIGN §4): it turns a command's typed domain Result into exactly one well-formed envelope on stdout and the matching process exit code, and it is the SOLE place envelopes are assembled, serialized, and exit codes computed (domain packages return plain structs and never touch the wire).
Package cli is wi's uniform command pipeline (DESIGN §4): it turns a command's typed domain Result into exactly one well-formed envelope on stdout and the matching process exit code, and it is the SOLE place envelopes are assembled, serialized, and exit codes computed (domain packages return plain structs and never touch the wire).
cli/opid
Package opid mints and validates wi operation identifiers (op_id), the single volatile field in the envelope (DESIGN §2 determinism: an envelope is byte-identical across runs modulo op_id and timestamps; DESIGN §3.1, §8).
Package opid mints and validates wi operation identifiers (op_id), the single volatile field in the envelope (DESIGN §2 determinism: an envelope is byte-identical across runs modulo op_id and timestamps; DESIGN §3.1, §8).
clock
Package clock is wi's injectable seam for the two volatile inputs that would otherwise make output non-reproducible: wall-clock time and randomness (DESIGN §2 determinism, §4).
Package clock is wi's injectable seam for the two volatile inputs that would otherwise make output non-reproducible: wall-clock time and randomness (DESIGN §2 determinism, §4).
config
Package config is the SOLE owner of wi's committed declarative manifest, <root>/wi.config.jsonc (DESIGN §1, §map).
Package config is the SOLE owner of wi's committed declarative manifest, <root>/wi.config.jsonc (DESIGN §1, §map).
contract
Package contract is the SOLE owner of wi's wire contract: the closed enums, exit codes, capability vocabulary, and (in later units) the Envelope wire type.
Package contract is the SOLE owner of wi's wire contract: the closed enums, exit codes, capability vocabulary, and (in later units) the Envelope wire type.
doctor
Package doctor is the read-only health-diagnosis domain behind `wi doctor` (alias `wi check`), the last HEAL layer (HEAL-8, DESIGN §7.5).
Package doctor is the read-only health-diagnosis domain behind `wi doctor` (alias `wi check`), the last HEAL layer (HEAL-8, DESIGN §7.5).
exitcontract
Package exitcontract is the single chokepoint between a command's typed outcome and the process exit code (DESIGN §3.2, IMPLEMENTATION_PLAN Wave A).
Package exitcontract is the single chokepoint between a command's typed outcome and the process exit code (DESIGN §3.2, IMPLEMENTATION_PLAN Wave A).
fault
Package fault is wi's deterministic fault-injection seam: the WI_FAULT half of the (guard → mutant) methodology (IMPLEMENTATION_PLAN §1).
Package fault is wi's deterministic fault-injection seam: the WI_FAULT half of the (guard → mutant) methodology (IMPLEMENTATION_PLAN §1).
gc
Package gc is wi's evidence-positive garbage-collection core (HEAL-2, DESIGN §7.1).
Package gc is wi's evidence-positive garbage-collection core (HEAL-2, DESIGN §7.1).
git
Package git provides wi's deterministic, typed git verbs on top of internal/gitexec (DESIGN §4).
Package git provides wi's deterministic, typed git verbs on top of internal/gitexec (DESIGN §4).
gitexec
Package gitexec is the single chokepoint through which wi launches every git child process (DESIGN §4, §2.3).
Package gitexec is the single chokepoint through which wi launches every git child process (DESIGN §4, §2.3).
help
Package help is wi's SOLE owner of the progressive-disclosure help model and the next[] follow-up rules (DESIGN §3.1 "internal/help — progressive-disclosure help model + next[] rules (SOLE owner)" / IMPLEMENTATION_PLAN M3).
Package help is wi's SOLE owner of the progressive-disclosure help model and the next[] follow-up rules (DESIGN §3.1 "internal/help — progressive-disclosure help model + next[] rules (SOLE owner)" / IMPLEMENTATION_PLAN M3).
host
Package host derives stable facts about the machine and the current boot that wi stamps into lock-holder identity — the {pid, host, boot_id, op_id} body of a lock file (DESIGN §6 / §7.3).
Package host derives stable facts about the machine and the current boot that wi stamps into lock-holder identity — the {pid, host, boot_id, op_id} body of a lock file (DESIGN §6 / §7.3).
invariants
Package invariants holds architecture-level fitness guards that constrain the whole module rather than any single package — the DESIGN.md §2 invariants.
Package invariants holds architecture-level fitness guards that constrain the whole module rather than any single package — the DESIGN.md §2 invariants.
isolate
Package isolate is the domain core of `wi isolate new`: it materializes one detached worktree per declared repo off the SSOT base, recording progress in internal/state as it goes (DESIGN §1, §6.3).
Package isolate is the domain core of `wi isolate new`: it materializes one detached worktree per declared repo off the SSOT base, recording progress in internal/state as it goes (DESIGN §1, §6.3).
journal
This file is the offline roll-forward executor of HEAL-4 (DESIGN §7.4, PLAN 76-77, decision #4).
This file is the offline roll-forward executor of HEAL-4 (DESIGN §7.4, PLAN 76-77, decision #4).
land
Package land returns an isolate's committed work into the SSOT base — the inverse of sync (DESIGN §1, §7.2).
Package land returns an isolate's committed work into the SSOT base — the inverse of sync (DESIGN §1, §7.2).
landstate
Package landstate is the SOLE owner of wi's durable parked-land accounting under <root>/.wi/land/<task>.json (DESIGN §map line 162, §7.2).
Package landstate is the SOLE owner of wi's durable parked-land accounting under <root>/.wi/land/<task>.json (DESIGN §map line 162, §7.2).
layout
Package layout is the SOLE owner of every filesystem path under a wi project root (DESIGN §1, §4).
Package layout is the SOLE owner of every filesystem path under a wi project root (DESIGN §1, §4).
lock
Package lock is the SOLE owner of wi's advisory lock-key namespace and the single total order in which any set of those keys is acquired (DESIGN §6.1).
Package lock is the SOLE owner of wi's advisory lock-key namespace and the single total order in which any set of those keys is acquired (DESIGN §6.1).
lockfs
Package lockfs owns wi's two filesystem-durability primitives: the SINGLE atomic file writer every .wi/ state writer reuses (this file, DESIGN §6.2) and, in a sibling file, the advisory flock used to serialize concurrent wi processes.
Package lockfs owns wi's two filesystem-durability primitives: the SINGLE atomic file writer every .wi/ state writer reuses (this file, DESIGN §6.2) and, in a sibling file, the advisory flock used to serialize concurrent wi processes.
mirror
Package mirror owns wi's cached SSOT freshness metadata (DESIGN §5).
Package mirror owns wi's cached SSOT freshness metadata (DESIGN §5).
recovery
Package recovery wires the journal's offline roll-forward executor to the domains that complete each kind of interrupted operation (HEAL-4 sub-unit 3d-iii, DESIGN §7.4).
Package recovery wires the journal's offline roll-forward executor to the domains that complete each kind of interrupted operation (HEAL-4 sub-unit 3d-iii, DESIGN §7.4).
resolve
Package resolve derives the path bundle behind `wi resolve <task>` (and the resolve block isolate responses embed) — DESIGN §3.1, §map line 166.
Package resolve derives the path bundle behind `wi resolve <task>` (and the resolve block isolate responses embed) — DESIGN §3.1, §map line 166.
state
Package state is the SOLE owner of wi's runtime registry under <root>/.wi/state/ (DESIGN §map line 168).
Package state is the SOLE owner of wi's runtime registry under <root>/.wi/state/ (DESIGN §map line 168).
suggest
Package suggest is wi's SOLE owner of the "did you mean" engine (DESIGN §3.1 / IMPLEMENTATION_PLAN M3): one hand-rolled Levenshtein distance plus the candidate selector the dispatcher uses to turn an unknown command into the envelope's did_you_mean[] hint.
Package suggest is wi's SOLE owner of the "did you mean" engine (DESIGN §3.1 / IMPLEMENTATION_PLAN M3): one hand-rolled Levenshtein distance plus the candidate selector the dispatcher uses to turn an unknown command into the envelope's did_you_mean[] hint.
sync
Package sync is the domain core of `wi sync`: it materializes and advances each requested repo's SSOT mirror to its origin base tip, then records what the fetch observed as a freshness Snapshot (DESIGN §5).
Package sync is the domain core of `wi sync`: it materializes and advances each requested repo's SSOT mirror to its origin base tip, then records what the fetch observed as a freshness Snapshot (DESIGN §5).
testenv
Package testenv is wi's hermetic real-git test harness (PLAN §M0).
Package testenv is wi's hermetic real-git test harness (PLAN §M0).
version
Package version derives wi's build identity from the binary itself — the module version, the toolchain, and the VCS stamps the Go toolchain embeds at build time.
Package version derives wi's build identity from the binary itself — the module version, the toolchain, and the VCS stamps the Go toolchain embeds at build time.
Package schema embeds the published wire-contract JSON Schema so it can be served by `wi schema` and validated against in tests without cwd-relative path lookups.
Package schema embeds the published wire-contract JSON Schema so it can be served by `wi schema` and validated against in tests without cwd-relative path lookups.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL