Add workspace strategy and runtime services plan

This commit is contained in:
Dotta 2026-03-09 12:52:48 -05:00
parent 84ef17bf85
commit 22761167c2

View file

@ -0,0 +1,728 @@
# 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.
## Recommended Configuration Surface
Introduce a generic execution workspace strategy in adapter config.
Example shape:
```json
{
"workspaceStrategy": {
"type": "project_primary"
}
}
```
Or:
```json
{
"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:
```json
{
"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:
```json
{
"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.
## Recommended Initial Scope
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