# Workspaces **Keywords:** workspace, multi-repo, many repositories, agent cwd, `.prowl/workspace.json`, workspace metadata, `prowl list` Workspaces let one agent work on a task that spans several repositories. A workspace is a folder added to Prowl as a runnable project, with metadata at `.prowl/workspace.json` describing the repositories inside it. When you open a workspace in Prowl: - The terminal starts in the workspace root. - The sidebar/detail view uses the workspace title and repository list from `.prowl/workspace.json`. - The `prowl` CLI reports the runnable target's `worktree.kind` as `workspace`. - Git worktree, branch, diff, and PR controls remain per-repository features; a workspace is intentionally a multi-repo working directory rather than a single git repository. Diff opens per child repository from its sidebar row (see below). ## Folder layout Use **Add...** from the sidebar toolbar and choose **Add Workspace**, or use the Worktrees menu or command palette to create a workspace. Prowl creates the shared folder, materializes the selected repositories, writes `.prowl/workspace.json`, and opens the workspace as a runnable folder. A workspace needs at least one repository; more can be added later (see [Editing a workspace](#editing-a-workspace)). The sheet has two levels. The **Workspace** panel holds the metadata: a **Title** (the folder name follows it until you press **Change…**), an optional **Description**, optional **Task Links** (URLs or issue keys, one per line), and the repository list with **Add Repository…**. Every row says in one line what Create will do with it ("Link → shares the live checkout at …", "New branch feature/x from main → worktree", "Clone git@… @ origin/main"), and a footer line summarizes the whole operation. Create is disabled until at least one repository is listed. **Add Repository…** opens the second level inside the same sheet, one repository at a time. First pick the source — **Opened in Prowl** (a list of the sidebar repositories), **Local Folder…** (an open panel), or **Remote URL** (branches load automatically once you stop typing or press Return). Then set **Name**, an optional **Role** (`app`, `backend`, `docs`, …), and the **Checkout**: **Link**, **New Branch** (name plus base branch), or **Existing Branch**. The folder name inside the workspace lives under **Advanced** and defaults to the repository name. **Add** returns to the workspace panel with the new row; **Back** discards it. All of this lands in `.prowl/workspace.json` so agents can read it. While a workspace is being created the prompt shows a spinner. **Cancel** stops the creation and rolls back everything created so far: cloned folders, created worktrees, and the workspace folder itself when Prowl created it. ```text my-feature-workspace/ ├─ .prowl/ │ └─ workspace.json ├─ app/ ├─ api/ └─ shared-package/ ``` Repository sources can be mixed in one workspace: - Already opened repositories can be inserted from the **Add Opened** menu (`source_kind: existing_path`). - Local repository folders are selected from disk (`source_kind: local_repository`). - Remote repositories are added through a URL prompt that loads remote heads before inserting the row. Loading can be canceled while the prompt is open. They are cloned into the workspace folder with `source_kind: remote`. The inserted row defaults to **Use Existing** on the detected default remote branch. - Bare repositories are supported by the metadata and materialization layer as `source_kind: bare_repository`, but the first workspace UI keeps that advanced source hidden. Opened and local repository rows show their source as read-only provenance rather than a mode selector because both follow the same materialization rules after they have been added. For already opened and local repositories, the branch action decides how the folder is materialized: - **Link** (the default) adds a symlink to the repository as it is on disk, so the workspace shares the live checkout. - **Create Branch** runs `git worktree add -b` against the source repository: the workspace gets an isolated checkout on a new branch created from the selected base ref, without touching the source repository's own checkout. The new worktree also appears in the source repository's worktree list. - **Use Existing** runs `git worktree add` with the selected ref. Choosing a remote-tracking ref creates a local tracking branch instead of a detached worktree. Git rejects a branch that is already checked out elsewhere. The creation prompt detects base-ref candidates for already opened and local repositories by reading local git refs, preferring the detected default branch such as `main` or `master`. Refs are grouped as local branches, remote tracking branches, or fetched remote branches, and the picker supports simple text search. `/HEAD` symbolic pointers are omitted from the picker because they only alias a branch that is already listed. Base refs are selected from detected refs so workspace creation does not try to checkout an arbitrary, nonexistent branch. When creation validation fails inside a repository row, the repository list scrolls to that row and highlights the invalid field with a red border. The **Folder** path follows the workspace **Title** while it is still generated by Prowl. Once you edit or choose the folder directly, Prowl treats it as a manual path and stops changing it when the title changes. Branch behavior is explicit: - **Create Branch** uses `branch_name` plus the selected base ref to create a new branch or worktree branch. A branch name is required in this mode for every source kind. - **Use Existing** uses the selected ref directly. For remote clones, Prowl checks out the selected remote branch after clone; Git creates the normal local tracking branch for refs such as `origin/feature`. For bare and local repositories, a local branch ref produces a branch worktree, and a remote-tracking ref such as `origin/feature` runs `git worktree add --track -B feature`, which creates the local tracking branch — aligning a same-named local branch to the remote when one exists. Git refuses if that branch is already checked out in another worktree. - When a remote-tracking ref is selected and a same-named local branch already exists, the repository row shows a choice: **Use local branch** (the default — checks out the existing local branch as-is) or **Reset to** the remote ref (the `-B` behavior, which discards local-only commits on that branch). This prevents an unnoticed reset of a local branch that is ahead of the remote. Workspace rows expand to show child repository rows. Each child row displays its current branch, uncommitted line counts, and pull request badge when available, including immediately after a newly created workspace is opened. Click a child row to select it and focus its terminal tab rooted at that repository folder inside the workspace, creating that tab the first time it is selected. Right-click a child row for **Copy Path** / **Reveal in Finder** / **Show Diff** / **Show Outgoing Changes** / **Edit Workspace…** / **Remove from Workspace…** (opens the editor with that repository already marked for removal). Diff works per child repository: click the child's `+N/-M` badge to open the diff for that repository, or, with a child selected, use `⌘⇧Y` (Show Diff), `⌘⌥⇧Y` (Show Outgoing Changes), or the matching Command Palette items. All configured diff tools apply; the Hunk tool opens a workspace terminal tab rooted at the child folder. See [diff-view](diff-view.md). ## Editing a workspace A workspace stays editable after creation. Open the editor from any of: - the sidebar: right-click the workspace row (or its `…` menu) → **Edit Workspace…**, or right-click a child row → **Edit Workspace…** / **Remove from Workspace…**; - Settings → the workspace under Repositories → **Edit Workspace…** (the editor opens as a sheet on the main window, which is brought to the front); - the Command Palette → **Edit Workspace** while a workspace or one of its children is selected; - the Worktrees menu → **Edit Workspace...** (enabled when a workspace is selected). The editor is the same two-level sheet as creation, pre-filled from `.prowl/workspace.json` (re-read from disk when it opens, so edits made outside Prowl are respected). It lets you change: - **Title**, **Description**, and **Task Links**. The title is display-only; the folder on disk keeps its name. - **Name** and **Role** of every existing repository: hover a row and press the pencil (or right-click → **Edit…**). Source and checkout are shown as a read-only summary; to change them, remove the repository and add it again. - **Order**: right-click a row → **Move Up** / **Move Down**, for existing and newly added rows alike. The sidebar and the metadata follow the new order. - **Added repositories**: **Add Repository…** with the same sources and checkouts as creation. They are materialized when you save and show "Added on save" until then. - **Removed repositories**: the trash button on a row (or **Remove from Workspace on save** inside the row's editor) marks it for removal; **Undo** restores it. By default only the metadata entry goes away and the folder stays. Tick **Also delete the folder inside the workspace** to remove what Prowl materialized: the symlink for a linked repository (the source is untouched), `git worktree remove --force` plus the folder for a worktree, or the cloned folder for a remote. Worktree entries with a recorded branch additionally offer **Delete branch … in the source repository**, which goes through the same protected-branch guard as the rest of Prowl. A workspace must keep at least one repository. Nothing changes until you press **Save**. Save runs in this order so the workspace is never left half-changed: added repositories are materialized first (and rolled back if one fails, leaving the old metadata untouched), then `.prowl/workspace.json` is rewritten, then removed repositories are cleaned up. Cleanup is best-effort: if a worktree cannot be unregistered, a repository path lies outside the workspace folder, or the entry has no recorded source (so Prowl cannot tell whether it created the folder), or a requested branch deletion is refused by git, the entry is still removed from the metadata and Prowl shows an alert listing what was left on disk. Save cannot be canceled once it has started. After a successful save the sidebar reloads and a **Workspace saved** toast appears. Paths inside the workspace are kept unique: adding a repository whose folder name is still occupied (for example by a member you are removing without deleting its folder) gets a `-2` suffix instead of failing. ## Removing a workspace **Remove Repository** on a workspace opens a dedicated confirmation. By default it only removes the entry from Prowl and leaves everything on disk. Tick **Also delete the workspace folder and its worktrees** to additionally unregister the worktrees that were created for this workspace from their source repositories (`git worktree remove --force`) and delete the workspace folder. Worktree entries with a recorded branch additionally offer a per-repository **Delete branch** checkbox that removes the branch from the source repository after the worktree is gone. Branch deletion goes through the same protected-branch guard as the rest of Prowl, so `main`, `master`, and the repository's default remote branch are never deleted even if they were recorded as a workspace branch. Linked repositories stay untouched — only the symlinks inside the workspace folder are removed. Cleanup is best-effort: a broken source repository is logged and skipped instead of blocking the deletion. If a worktree cannot be unregistered, Prowl asks before deleting the workspace folder, since deleting it anyway would leave a dangling worktree registration in the source repository. ## Metadata The workspace's repository settings page (Settings → the workspace under Repositories) shows this metadata and offers **Edit Workspace…**, which opens the editor described above. `.prowl/workspace.json` remains the source of truth: hand edits are picked up on the next reload, and when Prowl saves it keeps top-level and per-repository keys it does not know about, so fields written by other tools survive. Example `.prowl/workspace.json`: ```json { "schema_version": "prowl.workspace.v1", "title": "Checkout Flow", "description": "Update app UI, API contract, and shared package together.", "task_links": [ "https://github.com/onevcat/Prowl/issues/123" ], "repositories": [ { "name": "App", "role": "macOS app", "path": "app", "source_kind": "local_repository", "source_location": "/Users/mikoto/Documents/Repos/github/Prowl", "branch_name": "codex/checkout-flow" }, { "name": "API", "role": "backend", "path": "api", "source_kind": "remote", "source_location": "git@github.com:onevcat/api.git", "base_ref": "main" }, { "name": "Shared Package", "role": "library", "path": "shared-package", "source_kind": "bare_repository", "source_location": "/Users/mikoto/Documents/Repos/bare/shared-package.git", "branch_name": "codex/checkout-flow" } ] } ``` Top-level fields: - `schema_version` — metadata format version. Defaults to `prowl.workspace.v1` when omitted. - `id` — optional stable identifier. Defaults to the workspace root path. - `title` — display title. Defaults to the folder name. - `description` — optional task summary shown in the detail view. - `task_links` — optional links or identifiers for the work item. - `repositories` — repo entries that belong to the workspace. - `created_at` / `updated_at` — optional ISO-8601 timestamps. Repository entry fields: - `id` — optional stable identifier. Defaults to `path`. - `name` — display name. Defaults to the last path component. - `role` — optional short role such as `app`, `backend`, or `docs`. - `path` — relative path under the workspace root, or an absolute path. - `source_kind` — `existing_path`, `remote`, `local_repository`, or `bare_repository`. - `source_location` — optional remote URL, local repository path, or bare repo path. - `branch_name` — optional branch/worktree name expected for the task. - `base_ref` — optional base branch or ref. ## Agent usage Because the terminal cwd is the workspace root, agents can inspect and modify all listed repositories in one session: ```bash git -C app status git -C api status git -C shared-package status ``` Use the metadata as the contract: it tells the agent which repos are in scope, where they are on disk, and what role each repo plays in the task. ## Handing off between agents A workspace is the natural unit for a workflow handoff. `prowl.handoff` saves its durable packet under `.prowl/handoff/` and can launch a receiver through the standard workflow start flow. See [handoff](handoff.md).