Skip to content

feat(desktop): add persistent sub-workspaces for delegated agents - #5928

Open
mikigraf wants to merge 4 commits into
ParadevAI:mainfrom
mikigraf:feat/local-agent-fanout
Open

mikigraf wants to merge 4 commits into
ParadevAI:mainfrom
mikigraf:feat/local-agent-fanout

Conversation

@mikigraf

@mikigraf mikigraf commented Jul 24, 2026 •

Copy link
Copy Markdown

Relates to https://linear.app/superset-sh/issue/SUPER-1589/for-a-few-my-agents-eg-customer-suport-review-an-orchestrator-agent

What & why

Adds an opt-in Create sub-workspaces for sub-agents mode for agent delegation.

When enabled, delegated tasks become persistent, nested Superset sub-workspaces with their own workspace IDs, panes, and agent sessions. They share the parent workspace’s checkout and branch, so all changes remain on the parent branch.

The delegation mode is persisted per workspace and supplied to agents as hidden hook context without modifying the user’s prompt. Native/headless delegation remains the default.

image

How I tested it

  • Ran bun run lint.

  • Ran bun run typecheck across the monorepo.

  • Ran the complete desktop build with bun run --cwd apps/desktop build.

  • Ran 102 focused tests covering:

    • Sub-workspace creation, nesting, persistence, and deletion.
    • Shared branch and checkout behavior.
    • Sidebar placement and visibility.
    • Delegation preference persistence.
    • Hidden hook context and active-workspace mode updates.
    • Workspace cleanup behavior.
  • Ran a real Codex fan-out flow:

    • Requested exactly two sub-agents.
    • Confirmed two persistent Superset sub-workspaces were created.
    • Confirmed both shared the parent’s branch and checkout.
    • Killed both child agent terminals and confirmed their sub-workspaces remained.
  • Repeated the flow in native mode and confirmed no Superset child workspaces were created.

  • Confirmed the delegation implementation details did not appear in the user prompt or captured terminal output.

Screenshot/recording not included because the browser-control backend was unavailable during verification.

Checklist

  • PR title follows conventional commits (type(scope): subject)
  • bun run lint and bun run typecheck pass (CI fails on lint warnings too)
  • "Allow edits from maintainers" is checked on fork PRs

Summary by CodeRabbit

  • New Features
    • Added sub-workspaces with parent/descendant relationships, shared working tree/branch, and nested ordering in the sidebar.
    • Added workspaces create-subworkspace CLI command (supports attachments and delegation mode).
    • Added UI fan-out controls and an agent delegation mode toggle (including persisted preference), plus chat background-session auto-adoption for sub-workspaces.
  • Bug Fixes
    • Improved notify-hook delegation context for user prompt submission, including safer JSON escaping, correct delegation-mode handling, and more reliable host fallback behavior.
    • Updated sub-workspace deletion UI messaging and logic to match shared working tree/branch behavior.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e8e6d58a-57cd-4a0e-a880-56b82a8ef921

📥 Commits

Reviewing files that changed from the base of the PR and between 321b799 and 5bb0868.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/NewWorkspaceScreen.tsx

📝 Walkthrough

Walkthrough

This PR adds persistent subworkspace hierarchy and delegation modes across host-service storage and APIs, CLI commands, desktop workspace and sidebar UI, terminal environments, and notify hooks. It also adds descendant cleanup, chat-session adoption, visibility ordering, and related tests.

Changes

Subworkspace delegation

Layer / File(s) Summary
Workspace contracts and persistence
packages/shared/*, packages/host-service/drizzle/*, packages/host-service/src/db/*, packages/host-service/src/workspaces/*, packages/sdk/src/resources/workspaces.ts
Workspace records and snapshots support subworkspaces, parent relationships, and delegation modes; migrations and local storage persist and propagate these fields.
Host creation, delegation, and cleanup
packages/host-service/src/trpc/router/*, packages/cli/src/commands/workspaces/*, packages/cli/src/lib/host-target/*, packages/cli/src/lib/host/manifest.ts
Host procedures and the CLI create idempotent subworkspaces, launch agents with delegation modes, resolve local workspace targets, and clean up descendant sessions and records without deleting shared branches or worktrees.
Notify-hook delegation flow
apps/desktop/src/main/lib/agent-setup/*
The notify hook receives delegation mode from host responses, conditionally uses the v1 fallback, and emits escaped delegation context for UserPromptSubmit.
Sidebar hierarchy and deletion UI
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/*, apps/desktop/src/renderer/providers/*, apps/desktop/src/renderer/hooks/*
Subworkspaces are auto-included, ordered after parents, visually marked, and shown with subworkspace-specific deletion confirmation behavior.
Delegation preferences and workspace panes
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/*, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/*
Workspace creation and tab menus expose delegation controls; subworkspace pages auto-adopt chat sessions and reuse shared chat-pane focusing logic.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Desktop
  participant HostService
  participant WorkspaceStore
  participant Agent
  Desktop->>HostService: create or update workspace delegation mode
  HostService->>WorkspaceStore: persist workspace hierarchy and mode
  HostService->>Agent: launch with resolved delegation mode
  Agent->>HostService: create subworkspace through CLI/API
  HostService-->>Desktop: workspace snapshot and delegation mode
Loading

Possibly related PRs

Suggested reviewers: saddlepaddle

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is a clear conventional commit that matches the main change: persistent sub-workspaces for delegated agents.
Description check ✅ Passed The description follows the template and covers what/why, testing, and checklist items with concrete details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mikigraf
mikigraf marked this pull request as ready for review July 24, 2026 20:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/host-service/src/workspaces/local-workspace-store.ts (1)

207-226: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the descendant walk against parent cycles.

parentWorkspaceId is persisted directly, and a two-node cycle (A.parent = B, B.parent = A) is not ruled out. A subsequent branch/worktreePath/projectId patch makes updateLocalWorkspace chase parentWorkspaceId forever; pass an optional visited set and skip already-visited children before recursing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/host-service/src/workspaces/local-workspace-store.ts` around lines
207 - 226, Update updateLocalWorkspace to accept an optional visited set, record
the current workspace ID, and skip child IDs already present before recursive
calls. Pass the same set through each descendant update so cycles in
parentWorkspaceId terminate while preserving propagation of branch,
worktreePath, and projectId patches.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/DestroyConfirmPane/DestroyConfirmPane.tsx`:
- Around line 68-70: Update the isSubworkspace confirmation text in
DestroyConfirmPane to explicitly state that deleting the subworkspace also
removes its nested descendant subworkspaces and their agent sessions, while
preserving the existing note that the shared working tree and branch remain
unchanged.

In `@packages/host-service/src/db/schema.ts`:
- Around line 200-211: The schema declaration for parentWorkspaceId already
specifies SET NULL; update
packages/host-service/drizzle/0012_clean_edwin_jarvis.sql lines 1-2 to generate
the parent_workspace_id foreign key with ON DELETE SET NULL, keeping
packages/host-service/src/db/schema.ts lines 200-211 unchanged so the deployed
migration matches the model.

In
`@packages/host-service/src/trpc/router/workspace-creation/shared/adopt-existing-worktree.ts`:
- Around line 217-219: Update deleteLocalWorkspaceConflicts() to collect each
conflicting parent’s subworkspace descendants with
getSubworkspaceDescendants(...) before calling deleteLocalWorkspace(). Delete
the collected descendants first, then delete the conflicting parent, preserving
the existing filtering that excludes subworkspaces and keepWorkspaceId.

In `@packages/host-service/src/trpc/router/workspaces/workspaces.ts`:
- Around line 1014-1023: Prevent the re-create/adopt flow from resetting an
existing workspace’s agentDelegationMode to the schema default. In the workspace
creation logic surrounding updateLocalWorkspace, only reconcile
agentDelegationMode for freshly created rows; preserve the persisted mode for
rows returned by findExistingWorkspaceByBranch or adoptExistingWorktree paths.

---

Nitpick comments:
In `@packages/host-service/src/workspaces/local-workspace-store.ts`:
- Around line 207-226: Update updateLocalWorkspace to accept an optional visited
set, record the current workspace ID, and skip child IDs already present before
recursive calls. Pass the same set through each descendant update so cycles in
parentWorkspaceId terminate while preserving propagation of branch,
worktreePath, and projectId patches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f6f3bb82-13fc-4acd-aee5-5042e31b6692

📥 Commits

Reviewing files that changed from the base of the PR and between 9d659b4 and 9a5e401.

📒 Files selected for processing (68)
  • apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts
  • apps/desktop/src/main/lib/agent-setup/notify-hook.ts
  • apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
  • apps/desktop/src/renderer/commandPalette/core/types.ts
  • apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/index.ts
  • apps/desktop/src/renderer/hooks/host-workspaces/useHostWorkspaces/useHostWorkspaces.utils.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/DashboardSidebarDeleteDialog.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/DestroyConfirmPane/DestroyConfirmPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/hooks/useDestroyDialogState/useDestroyDialogState.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/buildDashboardSidebarProjects.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/AddTabMenu/AddTabMenu.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useAutoAdoptBackgroundSessions/useAutoAdoptBackgroundSessions.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useConsumeAutomationRunLink/useConsumeAutomationRunLink.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/focusChatPane/focusChatPane.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/focusChatPane/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/hooks/useRemoteHostStatus/useRemoteHostStatus.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/providers/WorkspaceProvider/WorkspaceProvider.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/usePlaceLocalWorktreesInSidebar/selectWorktreesToPlace.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useAgentDelegationPreference/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useAgentDelegationPreference/useAgentDelegationPreference.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useAgentDelegationPreference/useAgentDelegationPreference.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useSubmitWorkspace/useSubmitWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/sidebarMutations.ts
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useVisibleSidebarWorkspaceIds/useVisibleSidebarWorkspaceIds.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/sidebarVisibility.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/sidebarVisibility.ts
  • apps/desktop/src/renderer/stores/workspace-creates/useWorkspaceCreates.ts
  • apps/desktop/src/renderer/utils/getV2WorkspaceDisplayName/getV2WorkspaceDisplayName.ts
  • packages/cli/src/commands/workspaces/create-subworkspace/command.ts
  • packages/cli/src/lib/host-target/index.ts
  • packages/cli/src/lib/host-target/resolveHostTarget.ts
  • packages/cli/src/lib/host-target/resolveLocalWorkspaceTarget.ts
  • packages/cli/src/lib/host/manifest.test.ts
  • packages/cli/src/lib/host/manifest.ts
  • packages/host-service/drizzle/0012_clean_edwin_jarvis.sql
  • packages/host-service/drizzle/0013_persist_agent_delegation_mode.sql
  • packages/host-service/drizzle/meta/0012_snapshot.json
  • packages/host-service/drizzle/meta/0013_snapshot.json
  • packages/host-service/drizzle/meta/_journal.json
  • packages/host-service/src/db/schema.ts
  • packages/host-service/src/events/types.ts
  • packages/host-service/src/terminal/env.test.ts
  • packages/host-service/src/terminal/env.ts
  • packages/host-service/src/terminal/terminal.ts
  • packages/host-service/src/trpc/router/agents/agents.ts
  • packages/host-service/src/trpc/router/notifications/notifications.test.ts
  • packages/host-service/src/trpc/router/notifications/notifications.ts
  • packages/host-service/src/trpc/router/workspace-cleanup/is-main-workspace.ts
  • packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
  • packages/host-service/src/trpc/router/workspace-creation/shared/adopt-existing-worktree.ts
  • packages/host-service/src/trpc/router/workspace/workspace.ts
  • packages/host-service/src/trpc/router/workspaces/workspaces.ts
  • packages/host-service/src/workspaces/local-workspace-store.test.ts
  • packages/host-service/src/workspaces/local-workspace-store.ts
  • packages/host-service/test/helpers/seed.ts
  • packages/host-service/test/integration/subworkspace.integration.test.ts
  • packages/host-service/test/workspace-cleanup.test.ts
  • packages/sdk/src/resources/workspaces.ts
  • packages/shared/package.json
  • packages/shared/src/agent-delegation.test.ts
  • packages/shared/src/agent-delegation.ts

Comment thread packages/host-service/src/db/schema.ts Resolved
…nout

# Conflicts:
#	apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/NewWorkspaceScreen.tsx (2)

420-420: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Allow linked context or attachments to submit.

The UI requires non-empty prompt text, but useSubmitWorkspace intentionally supports linked PRs, issues, and attachments as creation context. A context-only workspace cannot be created from this screen.

Proposed fix
+	const hasCreationContext =
+		!isPromptEmpty ||
+		draft.linkedPR !== null ||
+		draft.linkedIssues.length > 0 ||
+		attachments.files.length > 0;
+
-		if (isPromptEmpty) return;
+		if (!hasCreationContext) return;
...
-								disabled={needsSetup || isPromptEmpty}
+								disabled={needsSetup || !hasCreationContext}

Also applies to: 695-695

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/NewWorkspaceScreen.tsx`
at line 420, Update the submit guards in NewWorkspaceScreen, including both
occurrences of isPromptEmpty, so submission is blocked only when the prompt is
empty and no linked context or attachments are present. Reuse the existing state
or helper symbols that represent linked PRs, issues, and attachments, while
preserving the current prompt-only submission behavior.

198-200: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle cached projects before readiness.

This guard ignores usable cached rows until areProjectsReady becomes true. Only wait for readiness when projects is empty, so project preselection remains cache-first.

Proposed fix
-		if (!isOpen || !areProjectsReady) return;
+		if (!isOpen) return;
+		if (projects.length === 0 && !areProjectsReady) return;

As per coding guidelines, “TanStack DB and Electric useLiveQuery rendering must be cache-first.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/NewWorkspaceScreen.tsx`
around lines 198 - 200, Update the useEffect guard in NewWorkspaceScreen so it
only waits for areProjectsReady when projects is empty; allow the effect to
process non-empty cached projects immediately, preserving cache-first project
preselection while retaining the existing isOpen check.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/NewWorkspaceScreen.tsx`:
- Line 420: Update the submit guards in NewWorkspaceScreen, including both
occurrences of isPromptEmpty, so submission is blocked only when the prompt is
empty and no linked context or attachments are present. Reuse the existing state
or helper symbols that represent linked PRs, issues, and attachments, while
preserving the current prompt-only submission behavior.
- Around line 198-200: Update the useEffect guard in NewWorkspaceScreen so it
only waits for areProjectsReady when projects is empty; allow the effect to
process non-empty cached projects immediately, preserving cache-first project
preselection while retaining the existing isOpen check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c8d34c8-8aa2-40d6-9ee0-23faa4c4ea28

📥 Commits

Reviewing files that changed from the base of the PR and between 2a8b295 and 321b799.

📒 Files selected for processing (5)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/NewWorkspaceScreen/NewWorkspaceScreen.tsx
  • packages/host-service/src/terminal/terminal.ts
  • packages/host-service/src/trpc/router/agents/agents.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx
  • packages/host-service/src/terminal/terminal.ts
  • packages/host-service/src/trpc/router/agents/agents.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant