Rewrite resolveWorkspace to per-task lockMode inference; drop company_fallback silent share (B4-adjacent)

Old resolver: task → agent → project → any company workspace, all
marked exclusive. That fourth step folded every runnable agent in a
multi-agent company onto a single workspace row and serialised the
C-suite (four agents, one exclusive lock, three starved).

New resolver: lockMode='exclusive' is now an opt-in decision inferred
per task. Three rules:

  1. task.workspaceId set → exclusive on that workspace (explicit).
  2. filesystem signal (task.requiresFilesystem OR agent in a
     coding role/engineering department) → exclusive on
     agent.workspaceId / project primary; throws if neither exists.
  3. otherwise → none / null. Pure cognitive work runs with no
     workspace lock and fully in parallel.

Removed:
  - company_fallback silent-share path. Filesystem-touching runs
    with no legitimate binding now throw WorkspaceConfigurationError
    at resolve time so the caller decides recovery rather than the
    resolver picking a random shared workspace.
  - workspacesMod.getAnyCompanyWorkspace (now dead code).

Invariants now enforced by types:
  - lockMode='exclusive' ⇒ workspace is a live WorkspaceRecord.
  - lockMode='none'      ⇒ workspace is null.

Tests updated to cover the new decision table and the four-non-
coding-agents parallel case that was the original bug.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-20 15:32:01 -04:00
1 parent 7829acc7f0
commit 28cdf7c56f
4 files changed
+252 -80

No files matched your search

-9
View File
@@ -68,15 +68,6 @@ export function buildWorkspaceMethods(
return rows.map((r) => r.localPath).filter(Boolean);
},
getAnyCompanyWorkspace(companyId: string): WorkspaceRecord | null {
const row = getDb()
.prepare(
"select id, company_id as companyId, name, local_path as localPath, repo_url as repoUrl, repo_ref as repoRef, project_id as projectId, state, is_primary as isPrimary, created_at as createdAt, updated_at as updatedAt from workspaces where company_id = ? order by is_primary desc, created_at asc limit 1",
)
.get(companyId) as WorkspaceRecord | undefined;
return row ? { ...row, isPrimary: toBoolean(row.isPrimary) } : null;
},
deleteWorkspace(id: string, companyId: string) {
transaction(() => {
getDb().prepare("update agents set workspace_id = null where workspace_id = ? and company_id = ?").run(id, companyId);