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
prowlCLI reports the runnable target'sworktree.kindasworkspace. - 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).
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.
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 -bagainst 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 addwith 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. <remote>/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_nameplus 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 asorigin/featurerunsgit 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
-Bbehavior, which discards local-only commits on that branch). This prevents an unnoticed reset of a local branch that is ahead of the remote.
- 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
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.
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 --forceplus 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:
{
"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 toprowl.workspace.v1when 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 topath.name— display name. Defaults to the last path component.role— optional short role such asapp,backend, ordocs.path— relative path under the workspace root, or an absolute path.source_kind—existing_path,remote,local_repository, orbare_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:
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.