Skip to content

feat: add workspace project lifecycle teardown#2243

Closed
neversettle17-101 wants to merge 2 commits into
ao/agent-orchestrator-19/workspace-projects-backendfrom
ao/agent-orchestrator-19/multi-worktree-lifecycle
Closed

feat: add workspace project lifecycle teardown#2243
neversettle17-101 wants to merge 2 commits into
ao/agent-orchestrator-19/workspace-projects-backendfrom
ao/agent-orchestrator-19/multi-worktree-lifecycle

Conversation

@neversettle17-101

Copy link
Copy Markdown
Collaborator

Summary

  • extend the workspace project adapter port with per-repo destroy, force-destroy, restore, preserve, and apply operations
  • route kill, cleanup, shutdown, reconcile, and restore-all through session_worktrees for workspace projects
  • preserve dirty workspace-project repos before force-removal, mark successful interactive removals non-restorable, and mark failed removals retry_remove
  • move stray restore paths aside before recreating workspace-project worktrees

Scope

This is the next backend milestone after workspace-aware spawn in #2224: multi-worktree kill/cleanup/shutdown/restore using session_worktrees rows. It does not include frontend work, workspace-aware SCM observation, root land/discard APIs, or full workspace status aggregation.

Tests

  • cd backend && go test ./internal/session_manager
  • cd backend && go test ./internal/adapters/workspace/gitworktree
  • cd backend && go test ./internal/storage/sqlite/store -run 'TestSessionWorktreesRoundTrip|TestUpsertSessionWorktreeEmptyStateDefaultsToActive'\n\nPart of Complete backend support for workspace projects #2222.

@neversettle17-101 neversettle17-101 force-pushed the ao/agent-orchestrator-19/multi-worktree-lifecycle branch from ee6b937 to 4852308 Compare June 27, 2026 18:15

@neversettle17-101 neversettle17-101 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review: requesting changes.

The per-repo lifecycle (DestroyWorkspaceProjectWorktree, Restore*, Stash*, ApplyPreserved*) and the manager fan-out across Kill / Cleanup / SaveAndTeardownAll / RestoreAll are well factored, and the state-machine column values used (active/removed/unavailable/retry_remove) all satisfy the session_worktrees.state CHECK constraint. Test coverage is good. Two issues should be resolved before merge:

  1. Kill of a dirty workspace project silently force-destroys the worktree, breaking the safety contract that single-repo Kill upholds. In Kill, a single-repo session with uncommitted changes returns (false, nil) and leaves the worktree intact (m.workspace.Destroy refuses on ErrWorkspaceDirty). The workspace path instead stashes the dirty work to a preserved ref, marks the row unavailable, and ForceDestroys the worktree — so the user's working tree is removed where the single-repo equivalent would have protected it. Worse, RestoreAll only replays rows in state removed (restorableWorktreeRows), so the unavailable preserved ref captured here is never auto-restored — the work is recoverable only by digging the git ref out manually. Please either make workspace Kill refuse-on-dirty to match single-repo semantics, or confirm this destructive divergence is intended and surface the preserved ref to the user. (see inline)

  2. Stale comments now contradict the code. This PR turns session_worktrees.state into a live lifecycle field, but the explanatory comments in backend/internal/domain/project.go (the State field: "never set by any live code path and always resolves to the column default") and backend/internal/storage/sqlite/store/session_worktree_store.go (UpsertSessionWorktree/sessionWorktreeFromGen: "unused multi-repo scaffolding ... no live code path sets ... State") still describe it as dead scaffolding. Those files aren't in this diff, but the PR makes their comments false and will mislead the next reader — please update them as part of this change.

Minor: sessionWorktreeRowsToRepoInfos returns a hard error if a worktree row references a child repo that has since been deregistered from the project, which aborts Kill/Cleanup/Restore for the whole session. Worth tolerating an orphaned child row (e.g. skip-with-warn) so one deregistered repo can't strand a session.

Comment thread backend/internal/session_manager/manager.go
Comment thread backend/internal/session_manager/manager.go
@neversettle17-101

Copy link
Copy Markdown
Collaborator Author

Addressed review 4598893233 in cbf35e8.

  • Workspace-project Kill now matches single-repo dirty-worktree semantics: ErrWorkspaceDirty returns freed=false without stashing or ForceDestroying the worktree.
  • Cleanup keeps the existing preserve/retry behavior via an explicit preserveDirty path.
  • Updated the stale session_worktrees.state comments in domain and SQLite store code to describe the live lifecycle states.
  • Also handled the minor stale child repo row case by logging and skipping unregistered child rows.

Validation: cd backend && go test ./...

@neversettle17-101 neversettle17-101 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Re-review: approve. All three points from the previous round are resolved by fix: preserve dirty workspace project kills.

  1. Kill safety contract restored. destroyWorkspaceProjectRows now takes a preserveDirty flag: Kill passes false, so a dirty worktree returns ErrWorkspaceDirty and Kill maps it to (false, nil) — refusing and leaving the worktree intact, matching single-repo Kill. Cleanup passes true to keep the stash-and-force-destroy behavior, which is the right split.
  2. Stale comments fixed. The State field doc in domain/project.go and both comments in session_worktree_store.go now correctly describe state as a live lifecycle field.
  3. Deregistered child repo no longer strands the session. sessionWorktreeRowsToRepoInfos now warn-logs and skips an unregistered repo row instead of hard-erroring.

Verified: packages build and internal/session_manager tests pass. One small non-blocking note inline.

if err := adapter.DestroyWorkspaceProjectWorktree(ctx, rows[i]); err != nil {
preservedRef := ""
if errors.Is(err, ports.ErrWorkspaceDirty) {
if !preserveDirty {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Non-blocking: with preserveDirty=false, this returns on the first dirty repo, but any clean child processed earlier in the reverse loop has already been removed and marked unavailable. So a refused Kill of a multi-repo session can leave a partial teardown (some repos gone, session still live). A retry recovers cleanly since DestroyWorkspaceProjectWorktree is a no-op on an already-removed path, so this is fine to leave — just flagging that the refusal isn't atomic across repos.

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