fix(workspace): enforce isolation policy, surface degraded mode in TUI#249
Open
windoliver wants to merge 5 commits intomainfrom
Open
fix(workspace): enforce isolation policy, surface degraded mode in TUI#249windoliver wants to merge 5 commits intomainfrom
windoliver wants to merge 5 commits intomainfrom
Conversation
Fixes #208. Workspace provisioning failures in SessionOrchestrator and SpawnManager were silently swallowed, causing agents to run in a shared project root with no operator visibility. Changes: - workspace-provisioner: fully async via execFileAsync (no event-loop block, no shell injection); parallel cleanup with Promise.allSettled - WorkspaceMode tagged union (isolated_worktree | fallback_workspace | bootstrap_failed) + WorkspaceIsolationPolicy (strict | allow-fallback) exported from core/index - session-orchestrator: provisionAgentWorkspace() helper with exhaustive policy-aware control flow; workspaceMode on AgentSessionInfo; default policy strict - spawn-manager: provisionWorkspace() replaces inline execSync git worktree; setIsolationPolicy(); workspaceMode on SpawnResult; default policy allow-fallback (TUI backward compat) - session-service: threads workspaceIsolationPolicy through to orchestrator - spawn-progress: [shared workspace] / [no config] badge + ⚠ reason line for degraded agents; screen-manager threads workspaceMode from SpawnResult - Tests: workspace-bootstrap unit tests (7), policy enforcement tests in orchestrator (5) and spawn-manager (2), retrofitted all existing spawn tests with workspaceMode assertions - E2E: tmux capture-pane test (3 scenarios) verifies badge visible on screen for fallback_workspace, hard failure for strict, clean for isolated_worktree
delegates/feeds/escalates edges now cause the target role's worktree to
branch off the source role's grove branch instead of HEAD.
coder → reviewer (delegates)
coder worktree: grove/<sessionId>/coder (base: HEAD)
reviewer worktree: grove/<sessionId>/reviewer (base: grove/<sessionId>/coder)
This gives reviewers a real git branch relationship — they can run
`git merge grove/<sessionId>/coder` to get the coder's latest commits
without touching the main checkout.
Other edge types (reports, requests, feedback) remain independent (HEAD).
Changes:
- topology: WORKSPACE_BRANCH_EDGES constant, resolveRoleWorkspaceStrategies(),
topologicalSortRoles() — Kahn's algorithm ensures source branches exist
before dependents try to base their worktrees on them
- session-orchestrator: provision workspaces in topological order then spawn
in parallel; passes resolved baseBranch to provisionAgentWorkspace()
- grove-md-builder: annotates edge_type lines with workspace behaviour comment
so GROVE.md is self-documenting
- sqlite: worktree_strategy_json column on sessions table (migration + DDL);
stores resolved strategies at creation time for operator visibility
- session: worktreeStrategies field on Session type
- tests: 13 new unit tests for resolveRoleWorkspaceStrategies and
topologicalSortRoles covering all 6 edge types + chain ordering
SpawnManager now uses resolveRoleWorkspaceStrategies() to pick the correct baseBranch per role based on topology edge types. ScreenManager passes the resolved topology to SpawnManager before spawning begins. Also fixes the branch-naming mismatch: spawn() now uses wsSessionId (stable session-level ID) for provisionWorkspace so the branch computed by resolveRoleWorkspaceStrategies matches the branch actually created for the source role. Validated via 3 tmux capture-pane scenarios: 1. delegates + real git → both isolated_worktree, no badge 2. feedback + real git → both isolated_worktree, independent 3. delegates + no git → both fallback_workspace, [shared workspace] badge
The TUI screen-manager fired all role spawns concurrently, racing coder and reviewer worktree provisioning. With delegates edges the reviewer needs coder's git branch to exist first — concurrent spawns caused reviewer to fail with "git worktree add ... branch not found" and fall back to shared workspace. Fix: import topologicalSortRoles and spawn sequentially — source roles complete before dependents start. Validated via real TUI E2E: 1. grove up → New Session → review-loop → goal → launch 2. Spawn-progress shows: coder spawning → coder started → reviewer spawning → reviewer started 3. git worktree list confirms both worktrees on correct branches 4. Reviewer branch based on coder branch (delegates edge)
SessionOrchestrator hardcoded mcpServePath as join(projectRoot, "src/mcp/serve.ts") which only works when the project IS the grove repo. For any other project (e.g. /tmp/foo), agents got a non-existent MCP server path and couldn't call grove_submit_work, grove_submit_review, or grove_done. Fix: extract resolveMcpServePath() into shared utility that derives the path from process.argv[1] (the running grove CLI entry point), climbing 3 levels to find the grove installation directory. Falls back through dist/mcp/serve.js → src/mcp/serve.ts → import.meta.url variants. DRYs up SpawnManager which had the same logic inline in two places. Validated E2E: real claude agent via acpx in /tmp test project successfully called grove_submit_work — contribution CID stored in session_contributions with the correct session link.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #208.
Summary
SessionOrchestrator.spawnAgent()andSpawnManager.spawn()were silently swallowed — agents ran in the shared project root with no error surfacedcatchwith policy-awareWorkspaceIsolationPolicy(strict|allow-fallback) andWorkspaceModetagged union (isolated_worktree|fallback_workspace|bootstrap_failed)[shared workspace]/[no config]badge + inline⚠ <reason>for degraded agentsChanges
Core
workspace-provisioner: fully async viaexecFileAsync(no event-loop blocking, no shell injection); parallel cleanup withPromise.allSettled;WorkspaceMode+WorkspaceIsolationPolicyexported fromcore/indexsession-orchestrator: newprovisionAgentWorkspace()helper with exhaustive policy-aware control flow;workspaceModeonAgentSessionInfo; default policystrictsession-service: threadsworkspaceIsolationPolicythrough to orchestratorTUI
spawn-manager: replaces inlineexecSync git worktree addwithprovisionWorkspace();setIsolationPolicy();workspaceModeonSpawnResult; defaultallow-fallback(backward compat)spawn-progress:[shared workspace]/[no config]badge +⚠ reasonline for degraded agentsscreen-manager: threadsworkspaceModefromSpawnResultintoAgentSpawnStateTests
workspace-bootstrapunit testssession-orchestratorspawn-managerworkspaceModeassertionsallow-fallback+ non-git dir →● started [shared workspace]+⚠ Command failed: git worktree add ...strict+ non-git dir →✗ failed: Workspace provisioning failed for '...'allow-fallback+ real git repo →● started(no badge — happy path)Full suite: 4898 pass, 0 fail.
Test plan
bun test— 4898 passbun test tests/tui/workspace-isolation-e2e.test.ts— 3 pass (requires tmux)[shared workspace]badge● started