doc/plans/workspace-technical-implementation.md
This document translates workspace-product-model-and-work-product.md into an implementation-ready engineering plan.
It is intentionally concrete:
This is the implementation target for the first workspace-aware delivery slice.
These decisions are treated as settled for this implementation:
execution_workspaces table now.issues get explicit project_workspace_id and execution_workspace_id./instance/settings > Experimental > Workspaces.project_workspaces evolves in place rather than being replaced.adapter_managed and cloud_sandbox execution modes are in scope.The repo already has:
project_workspacesprojects.execution_workspace_policyissues.execution_workspace_settingsworkspace_runtime_servicesworkspace-runtime.tsThis implementation should build on that baseline rather than fork it.
Project workspace: durable configured codebase/root for a projectExecution workspace: actual runtime workspace used for one or more issuesWork product: user-facing output such as PR, preview, branch, commit, artifact, documentRuntime service: process or service owned or tracked for a workspaceCompatibility mode: existing behavior preserved for upgraded installs with no explicit workspace opt-inThe first slice should introduce three explicit layers:
Project workspace
Execution workspace
Issue work product
The issue remains the planning and ownership unit. The execution workspace remains the runtime unit. The work product remains the deliverable/output unit.
This repo already uses PAPERCLIP_DEPLOYMENT_MODE for auth/deployment behavior (local_trusted | authenticated).
Do not overload that variable for workspace execution topology.
Add a separate execution-host hint:
PAPERCLIP_EXECUTION_TOPOLOGY=local|cloud|hybridDefault:
localPurpose:
local
cloud
hybrid
This is a guardrail and defaulting aid, not a hard policy engine in the first slice.
Add a new Experimental section under /instance/settings.
experimental.workspaces: booleanRules:
falseWork Product tab if it would otherwise be emptyproject_workspacesCurrent table exists and should evolve in place.
source_type text not null default 'local_path'
local_path | git_repo | non_git_path | remote_manageddefault_ref text nullvisibility text not null default 'default'
default | advancedsetup_command text nullcleanup_command text nullremote_provider text null
github, openai, anthropic, customremote_workspace_ref text nullshared_workspace_key text null
repo_url, backfill source_type='git_repo'cwd, backfill source_type='local_path'source_type='remote_managed'repo_ref into default_ref(project_id, source_type)(company_id, shared_workspace_key) non-unique for future supportexecution_workspacesCreate a new durable table.
id uuid pkcompany_id uuid not nullproject_id uuid not nullproject_workspace_id uuid nullsource_issue_id uuid nullmode text not null
shared_workspace | isolated_workspace | operator_branch | adapter_managed | cloud_sandboxstrategy_type text not null
project_primary | git_worktree | adapter_managed | cloud_sandboxname text not nullstatus text not null default 'active'
active | idle | in_review | archived | cleanup_failedcwd text nullrepo_url text nullbase_ref text nullbranch_name text nullprovider_type text not null default 'local_fs'
local_fs | git_worktree | adapter_managed | cloud_sandboxprovider_ref text nullderived_from_execution_workspace_id uuid nulllast_used_at timestamptz not null default now()opened_at timestamptz not null default now()closed_at timestamptz nullcleanup_eligible_at timestamptz nullcleanup_reason text nullmetadata jsonb nullcreated_at timestamptz not null default now()updated_at timestamptz not null default now()company_id -> companies.idproject_id -> projects.idproject_workspace_id -> project_workspaces.id on delete set nullsource_issue_id -> issues.id on delete set nullderived_from_execution_workspace_id -> execution_workspaces.id on delete set null(company_id, project_id, status)(company_id, project_workspace_id, status)(company_id, source_issue_id)(company_id, last_used_at desc)(company_id, branch_name) non-uniqueissuesAdd explicit workspace linkage.
project_workspace_id uuid nullexecution_workspace_id uuid nullexecution_workspace_preference text null
inherit | shared_workspace | isolated_workspace | operator_branch | reuse_existingproject_workspace_id -> project_workspaces.id on delete set nullexecution_workspace_id -> execution_workspaces.id on delete set nullproject_workspace_id is set, it must belong to the issue's project and companyexecution_workspace_id is set, it must belong to the issue's companyexecution_workspace_id is set, the referenced workspace's project_id must match the issue's project_idissue_work_productsCreate a new durable table for outputs.
id uuid pkcompany_id uuid not nullproject_id uuid nullissue_id uuid not nullexecution_workspace_id uuid nullruntime_service_id uuid nulltype text not null
preview_url | runtime_service | pull_request | branch | commit | artifact | documentprovider text not null
paperclip | github | vercel | s3 | customexternal_id text nulltitle text not nullurl text nullstatus text not null
active | ready_for_review | approved | changes_requested | merged | closed | failed | archivedreview_state text not null default 'none'
none | needs_board_review | approved | changes_requestedis_primary boolean not null default falsehealth_status text not null default 'unknown'
unknown | healthy | unhealthysummary text nullmetadata jsonb nullcreated_by_run_id uuid nullcreated_at timestamptz not null default now()updated_at timestamptz not null default now()company_id -> companies.idproject_id -> projects.id on delete set nullissue_id -> issues.id on delete cascadeexecution_workspace_id -> execution_workspaces.id on delete set nullruntime_service_id -> workspace_runtime_services.id on delete set nullcreated_by_run_id -> heartbeat_runs.id on delete set null(company_id, issue_id, type)(company_id, execution_workspace_id, type)(company_id, provider, external_id)(company_id, updated_at desc)workspace_runtime_servicesThis table already exists and should remain the system of record for owned/tracked services.
execution_workspace_id uuid nullexecution_workspace_id -> execution_workspaces.id on delete set nullpackages/sharedAdd fields:
sourceTypedefaultRefvisibilitysetupCommandcleanupCommandremoteProviderremoteWorkspaceRefsharedWorkspaceKeyNew shared types:
ExecutionWorkspaceExecutionWorkspaceModeExecutionWorkspaceStatusExecutionWorkspaceProviderTypeNew shared types:
IssueWorkProductIssueWorkProductTypeIssueWorkProductStatusIssueWorkProductReviewStateAdd:
projectWorkspaceIdexecutionWorkspaceIdexecutionWorkspacePreferenceworkProducts?: IssueWorkProduct[]Replace the current narrow policy with a more explicit shape:
enableddefaultMode
shared_workspace | isolated_workspace | operator_branch | adapter_defaultallowIssueOverridedefaultProjectWorkspaceIdworkspaceStrategybranchPolicypullRequestPolicyruntimePolicycleanupPolicyDo not try to encode every possible provider-specific field in V1. Keep provider-specific extensibility in nested JSON where needed.
Update project workspace CRUD to handle the extended schema.
is_primary on siblingssource_type=remote_managed may have null cwdcwd or repo_urlcwd/repoUrl/repoRefUpdate create/update flows to handle explicit workspace binding.
Resolve defaults in this order:
projectWorkspaceId from requestproject.executionWorkspacePolicy.defaultProjectWorkspaceIdResolve executionWorkspacePreference:
inheritDo not create an execution workspace at issue creation time unless:
reuse_existing is explicitly chosen and executionWorkspaceId is providedOtherwise, workspace realization happens when execution starts.
projectWorkspaceId only if the workspace belongs to the same projectexecutionWorkspaceId only if it belongs to the same company and projectRefactor workspace-runtime.ts so realization produces or reuses an execution_workspaces row.
Input:
Output:
shared_workspace
isolated_workspace
operator_branch
adapter_managed
cwdcloud_sandbox
When reuse_existing is requested:
For compatibility mode and shared-workspace projects:
This avoids a special-case branch in later work product linkage.
When runtime services are started or reused:
execution_workspace_idproject_workspace_id, project_id, and issue_idWhen a runtime service yields a URL:
issue_work_products row of type runtime_service or preview_urlAdd a service for creating/updating issue_work_products.
pull_requestpreview_urlruntime_servicebranchcommitartifactdocumentFor V1, GitHub is the only provider with richer semantics.
Supported statuses:
draftready_for_reviewapprovedchanges_requestedmergedclosedRepresent these in status and review_state rather than inventing a separate PR table in V1.
Extend existing routes:
GET /projects/:id/workspacesPOST /projects/:id/workspacesPATCH /projects/:id/workspaces/:workspaceIdDELETE /projects/:id/workspaces/:workspaceIdsourceTypedefaultRefvisibilitysetupCommandcleanupCommandremoteProviderremoteWorkspaceRefAdd:
GET /companies/:companyId/execution-workspaces
projectIdprojectWorkspaceIdstatusissueIdreuseEligible=trueGET /execution-workspaces/:idPATCH /execution-workspaces/:id
Do not add top-level navigation for these routes yet.
Add:
GET /issues/:id/work-productsPOST /issues/:id/work-productsPATCH /work-products/:idDELETE /work-products/:idExtend existing create/update payloads to accept:
projectWorkspaceIdexecutionWorkspacePreferenceexecutionWorkspaceIdExtend GET /issues/:id to return:
projectWorkspaceIdexecutionWorkspaceIdexecutionWorkspacePreferencecurrentExecutionWorkspaceworkProducts[]Add support for:
experimental.workspacesThis is a UI gate only.
If there is no generic instance settings storage yet, the first slice can store this in the existing config/instance settings mechanism used by /instance/settings.
/instance/settingsAdd section:
Experimental
Enable WorkspacesWhen off:
Do not create a separate Code tab yet.
Ship inside existing project properties first.
Project WorkspacesExecution DefaultsProvisioningPull RequestsPreviews and RuntimeCleanupexperimental.workspaces=truesourceType=git_reporemote_managedWhen the workspace experimental flag is on and the selected project has workspace automation or workspaces:
Codebase
Execution mode
Project defaultShared workspaceIsolated workspaceOperator branchReuse existing execution workspaceThis control should query only:
Do not expose all execution workspaces in a noisy unfiltered list.
Add a Work Product tab when:
Add compact header chips:
Add a detail route but no nav item.
Linked from:
For local adapters:
For remote adapters:
cwdExamples:
V1 should support richer PR state tracking, but not a full review engine.
open_prmark_readydraftready_for_reviewapprovedchanges_requestedmergedclosedissue_work_products with type='pull_request'status and review_statemetadataThe migration posture is backward-compatible by default.
Migrate project_workspaces in place.
source_typerepo_ref to default_refDo not backfill project_workspace_id or execution_workspace_id on all existing issues.
Reason:
Interpret old issues as:
executionWorkspacePreference = inheritDo not attempt a perfect historical reconstruction of execution workspaces in the migration itself.
Instead:
project_workspacesexecution_workspacesissue_work_productsissuesworkspace_runtime_servicesMitigation:
Codebase and Execution modeMitigation:
project_workspaces migrationMitigation:
cwd optional for execution workspacesprovider_type and provider_refPAPERCLIP_EXECUTION_TOPOLOGY as a defaulting guardrailMitigation:
If we want the narrowest useful implementation:
project_workspacesexecution_workspacesissues with explicit workspace fieldsissue_work_productsWork Product tab with PR/preview/runtime service displayThis slice is enough to validate the model without yet building every provider integration or cleanup workflow.