diff --git a/workspace-server/internal/handlers/org.go b/workspace-server/internal/handlers/org.go index 5f57b24c..3f360faf 100644 --- a/workspace-server/internal/handlers/org.go +++ b/workspace-server/internal/handlers/org.go @@ -422,6 +422,16 @@ type OrgWorkspace struct { Tier int `yaml:"tier" json:"tier"` Template string `yaml:"template" json:"template"` FilesDir string `yaml:"files_dir" json:"files_dir"` + // Spawning gates whether this workspace (AND its descendants) gets + // provisioned during /org/import. Pointer so we can distinguish + // "explicitly set to false" from "unset" (default = spawn). Use case: + // the dev-tree org template declares the full team structure but a + // developer's local machine only has RAM for a subset; setting + // spawning: false on a leaf or a sub-tree root skips that branch + // entirely without editing the canonical template structure. + // Counted in countWorkspaces same as actual; subtree-skip happens + // at provision time in createWorkspaceTree. + Spawning *bool `yaml:"spawning,omitempty" json:"spawning,omitempty"` // SystemPrompt is an inline override. Normally each role's system-prompt.md // lives at `/system-prompt.md` and is copied via the files_dir // template-copy step; inline overrides that path for ad-hoc workspaces. diff --git a/workspace-server/internal/handlers/org_import.go b/workspace-server/internal/handlers/org_import.go index 55b05eb2..2e06479f 100644 --- a/workspace-server/internal/handlers/org_import.go +++ b/workspace-server/internal/handlers/org_import.go @@ -42,6 +42,20 @@ import ( // straight into the parent's child-coordinate space without doing a // canvas-wide absolute-position walk. func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, absX, absY, relX, relY float64, defaults OrgDefaults, orgBaseDir string, results *[]map[string]interface{}, provisionSem chan struct{}) error { + // spawning: false guard — skip this workspace AND all descendants. + // Pointer-typed so we distinguish "explicitly false" from "unset" + // (unset = default to spawn). The guard sits BEFORE any side effect + // (no DB row, no docker provision, no children recursion) so a + // false-spawning subtree is genuinely a no-op except for the log line. + // Use case: dev-tree org template ships the full role taxonomy but a + // developer's machine only has RAM for a subset; a per-workspace + // `spawning: false` lets them narrow without editing the parent + // template's structure. + if ws.Spawning != nil && !*ws.Spawning { + log.Printf("Org import: skipping workspace %q (spawning=false; %d descendant workspace(s) in subtree also skipped)", ws.Name, countWorkspaces(ws.Children)) + return nil + } + // Apply defaults runtime := ws.Runtime if runtime == "" {