nexus/doc/plans/workspace-strategy-and-git-worktrees.md

25 KiB

Workspace Strategy and Git Worktrees

Context

PAP-447 asks how Paperclip should support worktree-driven coding workflows for local coding agents without turning that into a universal product requirement.

The motivating use case is strong:

  • when an issue starts, a local coding agent may want its own isolated checkout
  • the agent may need a dedicated branch and a predictable path to push later
  • the agent may need to start one or more long-lived workspace runtime services, discover reachable ports or URLs, and report them back into the issue
  • the workflow should reuse the same Paperclip instance and embedded database instead of creating a blank environment
  • local agent auth should remain low-friction

At the same time, we do not want to hard-code "every agent uses git worktrees" into Paperclip:

  • some operators use Paperclip to manage Paperclip and want worktrees heavily
  • other operators will not want worktrees at all
  • not every adapter runs in a local git repository
  • not every adapter runs on the same machine as Paperclip
  • Claude and Codex expose different built-in affordances, so Paperclip should not overfit to one tool

Core Product Decision

Paperclip should model execution workspaces, not worktrees.

More specifically:

  • the durable anchor is the project workspace or repo checkout
  • an issue may derive a temporary execution workspace from that project workspace
  • one implementation of an execution workspace is a git worktree
  • adapters decide whether and how to use that derived workspace

This keeps the abstraction portable:

  • project workspace is the repo/project-level concept
  • execution workspace is the runtime checkout/cwd for a run
  • git worktree is one strategy for creating that execution workspace
  • workspace runtime services are long-lived processes or previews attached to that workspace

This also keeps the abstraction valid for non-local adapters:

  • local adapters may receive a real filesystem cwd produced by Paperclip
  • remote or cloud adapters may receive the same execution intent in structured form and realize it inside their own environment
  • Paperclip should not assume that every adapter can see or use a host filesystem path directly

Answer to the Main Framing Questions

Are worktrees for agents or for repos/projects?

They should be treated as repo/project-scoped infrastructure, not agent identity.

The stable object is the project workspace. Agents come and go, ownership changes, and the same issue may be reassigned. A git worktree is a derived checkout of a repo workspace for a specific task or issue. The agent uses it, but should not own the abstraction.

If Paperclip makes worktrees agent-first, it will blur:

  • agent home directories
  • project repo roots
  • issue-specific branches/checkouts

That makes reuse, reassignment, cleanup, and UI visibility harder.

How do we preserve optionality?

By making execution workspace strategy opt-in at the adapter/config layer, not a global invariant.

Defaults should remain:

  • existing project workspace resolution
  • existing task-session resume
  • existing agent-home fallback

Then local coding agents can opt into a strategy like git_worktree.

How do we make this portable and adapter-appropriate?

By splitting responsibilities:

  • Paperclip core resolves and records execution workspace state
  • a shared local runtime helper can implement git-based checkout strategies
  • each adapter launches its tool inside the resolved cwd using adapter-specific flags

This avoids forcing a Claude-shaped or Codex-shaped model onto all adapters.

It also avoids forcing a host-filesystem model onto cloud agents. A cloud adapter may interpret the same requested strategy as:

  • create a fresh sandbox checkout from repo + ref
  • create an isolated branch/workspace inside the provider's remote environment
  • ignore local-only fields like host cwd while still honoring branch/ref/isolation intent

What the Current Code Already Supports

Paperclip already has the right foundation for a project-first model.

Project workspace is already first-class

  • project_workspaces already exists in packages/db/src/schema/project_workspaces.ts
  • the shared ProjectWorkspace type already includes cwd, repoUrl, and repoRef in packages/shared/src/types/project.ts
  • docs already state that agents use the project's primary workspace for project-scoped tasks in docs/api/goals-and-projects.md

Heartbeat already resolves workspace in the right order

Current run resolution already prefers:

  1. project workspace
  2. prior task session cwd
  3. agent-home fallback

See server/src/services/heartbeat.ts.

Session resume is already cwd-aware

Both local coding adapters treat session continuity as cwd-bound:

  • Codex: packages/adapters/codex-local/src/server/execute.ts
  • Claude: packages/adapters/claude-local/src/server/execute.ts

That means the clean insertion point is before adapter execution: resolve the final execution cwd first, then let the adapter run normally.

Server-spawned local auth already exists

For server-spawned local adapters, Paperclip already injects a short-lived local JWT:

  • JWT creation: server/src/services/heartbeat.ts
  • adapter env injection:
    • packages/adapters/codex-local/src/server/execute.ts
    • packages/adapters/claude-local/src/server/execute.ts

The manual-local bootstrap path is still weaker in authenticated mode, but that is a related auth ergonomics problem, not a reason to make worktrees a core invariant.

Tooling Observations from Vendor Docs

The linked tool docs support a project-first, adapter-specific launch model.

Codex

  • Codex app has a native worktree concept for parallel tasks in git repos
  • Codex CLI documents running in a chosen working directory and resuming sessions from the current working directory
  • Codex CLI does not present a single first-class portable CLI worktree abstraction that Paperclip should mirror directly

Implication:

  • for codex_local, Paperclip should usually create/select the checkout itself and then launch Codex inside that cwd

Claude

  • Claude documents explicit git worktree workflows for parallel sessions
  • Claude CLI supports --worktree / -w
  • Claude sessions also remain tied to directory context

Implication:

  • claude_local can optionally use native --worktree
  • but Paperclip should still treat that as an adapter optimization, not the canonical cross-adapter model

Local vs Remote Adapters

This plan must explicitly account for the fact that many adapters are not local.

Examples:

  • local CLI adapters such as codex_local and claude_local
  • cloud-hosted coding agents such as Cursor cloud agents
  • future hosted Codex or Claude agent modes
  • custom sandbox adapters built on E2B, Cloudflare, or similar environments

These adapters do not all share the same capabilities:

  • some can use host git worktrees directly
  • some can clone a repo and create branches remotely
  • some may expose a virtual workspace concept with no direct git worktree equivalent
  • some may not allow persistent filesystem state at all

Because of that, Paperclip should separate:

  • execution workspace intent: what isolation/branch/repo behavior we want
  • adapter realization: how a specific adapter implements that behavior

Execution workspace intent

Paperclip should be able to express intentions such as:

  • use the project's primary workspace directly
  • create an isolated issue-scoped checkout
  • base work on a given repo ref
  • derive a branch name from the issue
  • expose one or more reachable preview or service URLs if runtime services are started

Adapter realization

Adapters should be free to map that intent into their own environment:

  • local adapter: create a host git worktree and run in that cwd
  • cloud sandbox adapter: clone repo into a sandbox, create a branch there, and return sandbox metadata
  • hosted remote coding agent: call provider APIs that create a remote workspace/thread bound to the requested branch/ref

The important constraint is that the adapter reports back the realized execution workspace metadata in a normalized shape, even if the underlying implementation is not a git worktree.

Proposed Model

Use three layers:

  1. project workspace
  2. execution workspace
  3. workspace runtime services
  4. adapter session

1. Project workspace

Long-lived repo anchor.

Examples:

  • ./paperclip
  • repo URL and base ref
  • primary checkout for a project

2. Execution workspace

Derived runtime checkout for a specific issue/run.

Examples:

  • direct use of the project primary workspace
  • git worktree derived from the project workspace
  • remote sandbox checkout derived from repo URL + ref
  • custom checkout produced by an adapter-specific script

3. Adapter session

Long-lived or semi-long-lived processes associated with a workspace.

Examples:

  • local web server
  • background worker
  • sandbox preview URL
  • test watcher
  • tunnel process

These are not specific to Paperclip. They are a common property of working in a dev workspace, whether local or remote.

4. Adapter session

Claude/Codex conversation continuity and runtime state, which remains cwd-aware and should follow the execution workspace rather than define it.

Introduce a generic execution workspace strategy in adapter config.

Example shape:

{
  "workspaceStrategy": {
    "type": "project_primary"
  }
}

Or:

{
  "workspaceStrategy": {
    "type": "git_worktree",
    "baseRef": "origin/main",
    "branchTemplate": "{{issue.identifier}}-{{slug}}",
    "worktreeParentDir": ".paperclip/instances/default/worktrees/projects/{{project.id}}",
    "cleanupPolicy": "on_merged",
    "startDevServer": true,
    "devServerCommand": "pnpm dev",
    "devServerReadyUrlTemplate": "http://127.0.0.1:{{port}}/api/health"
  }
}

Remote adapters may instead use shapes like:

{
  "workspaceStrategy": {
    "type": "isolated_checkout",
    "provider": "adapter_managed",
    "baseRef": "origin/main",
    "branchTemplate": "{{issue.identifier}}-{{slug}}"
  }
}

The important point is that git_worktree is a strategy value for adapters that can use it, not the universal contract.

Workspace runtime services

Do not model this as a Paperclip-specific devServer flag.

Instead, model it as a generic list of workspace-attached runtime services.

Example shape:

{
  "workspaceRuntime": {
    "services": [
      {
        "name": "web",
        "description": "Primary app server for this workspace",
        "command": "pnpm dev",
        "cwd": ".",
        "env": {
          "DATABASE_URL": "${workspace.env.DATABASE_URL}"
        },
        "port": {
          "type": "auto"
        },
        "readiness": {
          "type": "http",
          "urlTemplate": "http://127.0.0.1:${port}/api/health"
        },
        "expose": {
          "type": "url",
          "urlTemplate": "http://127.0.0.1:${port}"
        },
        "reuseScope": "project_workspace",
        "lifecycle": "shared",
        "stopPolicy": {
          "type": "idle_timeout",
          "idleSeconds": 1800
        }
      }
    ]
  }
}

This contract is intentionally generic:

  • command can start any workspace-attached process, not just a web server
  • database reuse is handled through env/config injection, not a product-specific special case
  • local and remote adapters can realize the same service intent differently

Service intent vs service realization

Paperclip should distinguish between:

  • service intent: what kind of companion runtime the workspace wants
  • service realization: how a local or remote adapter actually starts and exposes it

Examples:

  • local adapter:
    • starts pnpm dev
    • allocates a free host port
    • health-checks a localhost URL
    • reports { pid, port, url }
  • cloud sandbox adapter:
    • starts a preview process inside the sandbox
    • receives a provider preview URL
    • reports { sandboxId, previewUrl }
  • hosted remote coding agent:
    • may ask the provider to create a preview environment
    • reports provider-native workspace/service metadata

Paperclip should normalize the reported metadata without requiring every adapter to look like a host-local process.

Keep issue-level overrides possible through the existing assigneeAdapterOverrides shape in packages/shared/src/types/issue.ts.

Responsibilities by Layer

Paperclip Core

Paperclip core should:

  • resolve the base project workspace for the issue
  • resolve or request an execution workspace
  • resolve or request workspace runtime services when configured
  • inject execution workspace metadata into run context
  • persist enough metadata for board visibility and cleanup
  • manage lifecycle hooks around run start/finish where needed

Paperclip core should not:

  • require worktrees for all agents
  • assume every adapter is local and git-backed
  • assume every runtime service is a localhost process with a PID
  • encode tool-specific worktree prompts as core product behavior

Shared Local Runtime Helper

A shared server-side helper should handle local git mechanics:

  • validate repo root
  • create/select branch
  • create/select git worktree
  • allocate a free port
  • optionally start and track a dev server
  • return { cwd, branchName, url }

This helper can be reused by:

  • codex_local
  • claude_local
  • future local adapters like Cursor/OpenCode equivalents

This helper is intentionally for local adapters only. Remote adapters should not be forced through a host-local git helper.

Shared Runtime Service Manager

In addition to the local git helper, Paperclip should define a generic runtime service manager contract.

Its job is to:

  • decide whether a configured service should be reused or started fresh
  • allocate local ports when needed
  • start and monitor local processes when the adapter/runtime realization is host-local
  • record normalized service metadata for remote realizations
  • run readiness checks
  • surface service URLs and state to the board
  • apply shutdown policy

This manager should not be hard-coded to "dev servers". It should work for any long-lived workspace companion process.

Adapter

The adapter should:

  • accept the resolved execution cwd
  • or accept structured execution workspace intent when no host cwd is available
  • accept structured workspace runtime service intent when service orchestration is delegated to the adapter
  • launch its tool with adapter-specific flags
  • keep its own session continuity semantics

For example:

  • codex_local: run inside cwd, likely with --cd or process cwd
  • claude_local: run inside cwd, optionally use --worktree when it helps
  • remote sandbox adapter: create its own isolated workspace from repo/ref/branch intent and report the realized remote workspace metadata back to Paperclip

For runtime services:

  • local adapter or shared host manager: start the local process and return host-local metadata
  • remote adapter: create or reuse the remote preview/service and return normalized remote metadata

Minimal Data Model Additions

Do not create a fully first-class worktrees table yet.

Start smaller by recording derived execution workspace metadata on runs, issues, or both.

Suggested fields to introduce:

  • executionWorkspaceStrategy
  • executionWorkspaceCwd
  • executionBranchName
  • executionWorkspaceStatus
  • executionServiceRefs
  • executionCleanupStatus

These can live first on heartbeat_runs.context_snapshot or adjacent run metadata, with an optional later move into a dedicated table if the UI and cleanup workflows justify it.

For runtime services specifically, Paperclip should eventually track normalized fields such as:

  • serviceName
  • serviceKind
  • scopeType
  • scopeId
  • status
  • command
  • cwd
  • envFingerprint
  • port
  • url
  • provider
  • providerRef
  • startedByRunId
  • ownerAgentId
  • lastUsedAt
  • stopPolicy
  • healthStatus

The first implementation can keep this in run metadata if needed, but the long-term shape is a generic runtime service registry rather than one-off server URL fields.

Concrete Implementation Plan

Phase 1: Define Shared Contracts

  1. Introduce a shared execution workspace strategy contract in packages/shared.
  2. Add adapter-config schema support for:
    • workspaceStrategy.type
    • baseRef
    • branchTemplate
    • worktreeParentDir
    • cleanupPolicy
    • optional workspace runtime service settings
  3. Keep the existing useProjectWorkspace flag working as a lower-level compatibility control.
  4. Distinguish local realization fields from generic intent fields so remote adapters are not forced to consume host cwd values.
  5. Define a generic workspaceRuntime.services[] contract with:
    • service name
    • command or provider-managed intent
    • env overrides
    • readiness checks
    • exposure metadata
    • reuse scope
    • lifecycle
    • stop policy

Acceptance:

  • adapter config can express project_primary and git_worktree
  • config remains optional and backwards-compatible
  • runtime services are expressed generically, not as Paperclip-only dev-server flags

Phase 2: Resolve Execution Workspace in Heartbeat

  1. Extend heartbeat workspace resolution so it can return a richer execution workspace result.
  2. Keep current fallback order, but distinguish:
    • base project workspace
    • derived execution workspace
  3. Inject resolved execution workspace details into context.paperclipWorkspace for local adapters and into a generic execution-workspace intent payload for adapters that need structured remote realization.
  4. Resolve configured runtime service intent alongside the execution workspace so the adapter or host manager receives a complete workspace runtime contract.

Primary touchpoints:

  • server/src/services/heartbeat.ts

Acceptance:

  • runs still work unchanged when no strategy is configured
  • the resolved context clearly indicates which strategy produced the cwd

Phase 3: Add Shared Local Git Workspace Helper

  1. Create a server-side helper module for local repo checkout strategies.
  2. Implement git_worktree strategy:
    • validate git repo at base workspace cwd
    • derive branch name from issue
    • create or reuse a worktree path
    • detect collisions cleanly
  3. Return structured metadata:
    • final cwd
    • branch name
    • worktree path
    • repo root

Acceptance:

  • helper is reusable outside a single adapter
  • worktree creation is deterministic for a given issue/config
  • remote adapters remain unaffected by this helper

Phase 4: Optional Dev Server Lifecycle

Rename this phase conceptually to workspace runtime service lifecycle.

  1. Add optional runtime service startup on execution workspace creation.
  2. Support both:
    • host-managed local services
    • adapter-managed remote services
  3. For local services:
    • allocate a free port before launch when required
    • start the configured command in the correct cwd
    • run readiness checks
    • register the realized metadata
  4. For remote services:
    • let the adapter return normalized service metadata after provisioning
    • do not assume PID or localhost access
  5. Post or update issue-visible metadata with the service URLs and labels.

Acceptance:

  • runtime service startup remains opt-in
  • failures produce actionable run logs and issue comments
  • same embedded DB / Paperclip instance can be reused through env/config injection when appropriate
  • remote service realizations are represented without pretending to be local processes

Phase 5: Runtime Service Reuse, Tracking, and Shutdown

  1. Introduce a generic runtime service registry.
  2. Each service should be tracked with:
    • scopeType: project_workspace | execution_workspace | run | agent
    • scopeId
    • serviceName
    • status
    • command or provider metadata
    • cwd if local
    • envFingerprint
    • port
    • url
    • provider / providerRef
    • ownerAgentId
    • startedByRunId
    • lastUsedAt
    • stopPolicy
  3. Introduce a deterministic reuseKey, for example:
    • projectWorkspaceId + serviceName + envFingerprint
  4. Reuse policy:
    • if a healthy service with the same reuse key exists, attach to it
    • otherwise start a new service
  5. Distinguish lifecycle classes:
    • shared: reusable across runs, usually scoped to project_workspace
    • ephemeral: tied to execution_workspace or run
  6. Shutdown policy:
    • run scope: stop when run ends
    • execution_workspace scope: stop when workspace is cleaned up
    • project_workspace scope: stop on idle timeout, explicit stop, or workspace removal
    • agent scope: stop when ownership is transferred or agent policy requires it
  7. Health policy:
    • readiness check at startup
    • periodic or on-demand liveness checks
    • mark unhealthy before killing when possible

Acceptance:

  • Paperclip can decide whether to reuse or start a fresh service deterministically
  • local and remote services share a normalized tracking model
  • shutdown is policy-driven instead of implicit
  • board can understand why a service was kept, reused, or stopped

Phase 6: Adapter Integration

  1. Update codex_local to consume resolved execution workspace cwd.
  2. Update claude_local to consume resolved execution workspace cwd.
  3. Define a normalized adapter contract for remote adapters that receive execution workspace intent instead of a host-local cwd.
  4. Optionally allow Claude-specific optimization paths using native --worktree, but keep the shared server-side checkout strategy as canonical for local adapters.
  5. Define how adapters return runtime service realizations:
    • local host-managed service reference
    • remote provider-managed service reference

Acceptance:

  • adapter behavior remains unchanged when strategy is absent
  • session resume remains cwd-safe
  • no adapter is forced into git behavior
  • remote adapters can implement equivalent isolation without pretending to be local worktrees
  • adapters can report service URLs and lifecycle metadata in a normalized shape

Phase 7: Visibility and Issue Comments

  1. Expose execution workspace metadata in run details and optionally issue detail UI:
    • strategy
    • cwd
    • branch
    • runtime service refs
  2. Expose runtime services with:
    • service name
    • status
    • URL
    • scope
    • owner
    • health
  3. Add standard issue comment output when a worktree-backed or remotely isolated run starts:
    • branch
    • worktree path
    • service URLs if present

Acceptance:

  • board can see where the agent is working
  • board can see what runtime services exist for that workspace
  • issue thread becomes the handoff surface for branch names and reachable URLs

Phase 8: Cleanup Policies

  1. Implement cleanup policies:
    • manual
    • on_done
    • on_merged
  2. For worktree cleanup:
    • stop tracked runtime services if owned by the workspace lifecycle
    • remove worktree
    • optionally delete local branch after merge
  3. Start with conservative defaults:
    • do not auto-delete anything unless explicitly configured

Acceptance:

  • cleanup is safe and reversible by default
  • merge-based cleanup can be introduced after basic lifecycle is stable

Phase 9: Auth Ergonomics Follow-Up

This is related, but should be tracked separately from the workspace strategy work.

Needed improvement:

  • make manual local agent bootstrap in authenticated/private mode easier, so operators can become codexcoder or claudecoder locally without depending on an already-established browser-auth CLI context

This should likely take the form of a local operator bootstrap flow, not a weakening of runtime auth boundaries.

Rollout Strategy

  1. Ship the shared config contract and no-op-compatible heartbeat changes first.
  2. Pilot with codexcoder and claudecoder only.
  3. Test against Paperclip-on-Paperclip workflows first.
  4. Keep project_primary as the default for all existing agents.
  5. Add UI exposure and cleanup only after the core runtime path is stable.

Acceptance Criteria

  1. Worktree behavior is optional, not a global requirement.
  2. Project workspaces remain the canonical repo anchor.
  3. Local coding agents can opt into isolated issue-scoped execution workspaces.
  4. The same model works for both codex_local and claude_local without forcing a tool-specific abstraction into core.
  5. Remote adapters can consume the same execution workspace intent without requiring host-local filesystem access.
  6. Session continuity remains correct because each adapter resumes relative to its realized execution workspace.
  7. Workspace runtime services are modeled generically, not as Paperclip-specific dev-server toggles.
  8. Board users can see branch/path/URL information for worktree-backed or remotely isolated runs.
  9. Service reuse and shutdown are deterministic and policy-driven.
  10. Cleanup is conservative by default.

To keep this tractable, the first implementation should:

  • support only local coding adapters
  • support only project_primary and git_worktree
  • avoid a new dedicated database table for worktrees
  • start with a single host-managed runtime service implementation path
  • postpone merge-driven cleanup automation until after basic start/run/visibility is proven

That is enough to validate the local product shape without prematurely freezing the wrong abstraction.

Follow-up expansion after that validation:

  • define the remote adapter contract for adapter-managed isolated checkouts
  • add one cloud/sandbox adapter implementation path
  • normalize realized metadata so local and remote execution workspaces appear similarly in the UI
  • expand the runtime service registry from local host-managed services to remote adapter-managed services