workspace-decisions.md
(architectural rationale).
What is a workspace?
An execution workspace is an isolated filesystem + runtime sandbox used by an agent (or a human teammate) to execute a task. For engineering projects it is a git worktree pinned to the project’s base ref, branched onto a task-scoped branch, with its own runtime services (dev server, watchers, database). Each task gets its own worktree so concurrent runs never clobber each other’s files, branches, or ports. Non-engineering work types fall back toshared or none modes.
Stored in execution_workspaces
with JSONB metadata (denormalized config snapshot, linked issues, PR info,
close-report) + JSONB runtime (service state, ports, logs).
Enabling worktrees for a project
Three gates must line up:- Instance setting —
enableIsolatedWorkspacesmust betrue(the default). Toggle in Settings → Instance → Experimental. - Project type — the project’s
functionTypemust besoftware_development. Settings → Project → Properties → Function Type. - Project policy —
executionWorkspacePolicy.defaultModemust beisolated_workspace(the new-project default for software engineering). Also configurable under Project → Properties → Execution Workspace.
Per-task reuse_existing
A task can opt into a different execution mode than its project default via the Execution Workspace card on the TaskSlideOver (seeIssueWorkspaceCard). Three
per-task choices:
shared_workspace— run in the project’s shared directoryisolated_workspace— create a fresh worktree for this task (default)reuse_existing— pick an existing worktree from a dropdown; subsequent heartbeat runs short-circuitrealizeExecutionWorkspaceand reuse it
Workspace lifecycle
- Auto-create — on the first heartbeat run for an engineering task with
per_taskpolicy,workspace-runtimematerializes the worktree (git branch + directory + provision command) and writes a config snapshot intometadata.config. - Reuse — subsequent runs on the same task (or any task set to
reuse_existingon that workspace) short-circuit and hand the existing worktree to the adapter. - Archive — triggered manually via the close dialog (see below) or by a close workflow wired into task completion.
- TTL mark — the optional TTL sweeper marks stale workspaces as
cleanupEligibleAtonce the project’sttlDayshas elapsed sincelastUsedAt. The sweeper does not auto-archive; a founder or team lead still confirms.
The workspace cockpit
- Company-wide list:
/:companyPrefix/workspaces(sidebar: WORK → Workspaces) — seeWorkspacesList. Status chips, project grouping, last-used timestamp, kebab per row. - Detail view:
/:companyPrefix/workspaces/:workspaceId— 3-panel IDE-style cockpit (WorkspaceLayout): task nav + timeline/preview + context. - Header kebab menu: Settings Sheet + Archive dialog.
- Open in IDE: VS Code / Cursor / Zed buttons + Reveal in Finder/Explorer
- Copy path via
OpenInIdeButton. Preferred editor persists atlocalStorage["aoa:workspace:preferred-editor"].
- Copy path via
- Runtime services: start/stop/restart dev servers from the right panel’s
ServicesSection(gated tosoftware_developmentprojects).
Configuration via Settings Sheet
Kebab → Settings opensWorkspaceSettingsSheet
(three tabs: Configuration / Runtime Logs / Linked Issues). Editable fields:
name— display namerepoUrl+baseRef+branchName— git origin + checkout pointsproviderRef— GitHub PR metadata (auto-filled by Create PR flow)provisionCommand— run after worktree creation (e.g.pnpm install)cleanupCommand— run before directory removal on archiveteardownCommand— run on runtime stop (e.g. docker-compose down)
PATCH /execution-workspaces/:id (widened in
Task 10 to accept metadata-only updates).
Archiving
Kebab → Archive opens the close dialog (ExecutionWorkspaceCloseDialog),
built on the AlertDialog primitive. Shows a preview of planned actions:
archive_record— mark DB row archivedstop_runtime_services— halt running servicescleanup_command— run the configured cleanup commandteardown_command— run the configured teardown commandgit_worktree_remove—git worktree removegit_branch_delete— delete the task branchremove_local_directory—rm -rfthe worktree directory
team_member tries
to archive outside their scope.
Create PR
GitHub-only MVP.- Store a personal access token in Settings → Integrations → GitHub
(per-company; encrypted in
company_secretsasgithub_pat). PAT needsreposcope. - Open a workspace with a pushed branch on a GitHub repo.
- GitPanel shows a Create PR button — opens
CreatePrDialogprefilled from the linked issue. - On success, the PR link persists to
workspace.metadata.prand a “Opened PR #N” comment is posted to the linked task. The button changes to “PR #N” (external link) on subsequent loads.
TTL sweeper
Opt-in in Settings → Instance → Experimental →enableWorkspaceTtlSweeper.
A scheduler runs every 6 hours and sets cleanupEligibleAt on workspaces
older than their project’s ttlDays (set on Project → Properties →
Execution Workspace → TTL). The sweeper surfaces candidates in the
/workspaces list with a “Cleanup eligible” chip — it does not
auto-archive. Founders and team leads act on the list manually or via
future automation.
Role-based permissions
Enforced server-side inworkspace-authz + assertBoard (Task 9):
- founder — all operations on any workspace
- team_lead — all operations on workspaces in projects they lead (includes runtime control, archive, config PATCH, Create PR)
- team_member — read-only: can view
/workspaces, open detail, read the operations log (GET /execution-workspaces/:id/operations). Cannot start/stop services, archive, edit config, or create PRs — those return 403
Limitations / known issues
- Create PR is GitHub-only — GitLab/Bitbucket deferred;
providerRefschema is multi-provider-ready - Worktree cleanup is advisory — the TTL sweeper never deletes files; manual archive always required
- Single-PAT-per-company model — no per-user PATs; founder-owned token shared across team
- IDE launchers use OS
open— no deep URL handlers; behavior depends on local associations - Workspaces page breadcrumb reads as “Discussions” — backlog cleanup item
- Radix DialogTitle warnings appear in console on some dialogs (pre-existing)
- No idempotency guard on Create PR — double-click protected by button disable only; server-side idempotency key is a backlog item