Introduction

wsx is a terminal UI that runs Claude Code, Pi, Hermes, Codex, or oh-my-pi sessions in their own git worktrees. Use it to launch, watch, and steer many agent sessions in parallel from one dashboard, including several agents working together in one workspace.

Key features

  • Parallel sessions in worktrees: each workspace gets its own branch, worktree, and agent session. See Dashboard.
  • Multiple coding agents: pick Claude, Pi, Hermes, Codex, or oh-my-pi per workspace. See Coding agents.
  • Multi-agent workspaces: run several agents in one worktree and have them message each other. See Multi-agent workspaces.
  • Attention alerts: status glyphs, a terminal bell, and a live activity line show which sessions need you. See Status indicators.
  • Agent status and recaps: agents report their status and a goal/state/next recap with wsx status and wsx recap. See Status and recap.
  • Project manager digest: one pane summarizes what every workspace is for and what's next. See Project manager digest.
  • Configurable detail bar: up to four containers of built-in or custom modules. See Detail bar.
  • Tags and hiding: label workspaces and hide them by tag or PR state. See Tags and hiding.
  • Split panes and saved layouts: watch other workspaces while attached. See Updates panel and split panes.
  • Pinned commands and prompt tags: fire saved prompts with Ctrl-x <digit> and insert XML-wrapped prompt sections. See Pinned commands and Prompt tags.
  • Bundled agent skills: wsx, agent-review, and handoff. See Agent skills.
  • Editor, terminal, and diff hooks: open a workspace in your tools with one key. See Editor, terminal, and diff.
  • Editor-hosted agents: give an agent in your editor the workspace's context with wsx context. See Editor-hosted agents.
  • Related repos: let agents read sibling repos without editing them. See Related repos.
  • Remote and shared workspaces: attach over ssh, or run agents in tmux on this or another host. See Remote access and Shared workspaces.
  • Desktop widgets: a waybar module, a KDE Plasma applet, and a macOS menubar plugin. See Desktop widgets.
  • Less setup per workspace: auto-rename branches from your first prompt, and run per-repo setup and archive scripts. See Auto-rename and Setup and archive scripts.

Where to go next

Getting started

Install wsx, add a repository, and open your first workspace.

  • Installation: Homebrew, cargo-binstall, Nix, prebuilt binaries, or source.
  • Quick start: a first session, and wiring up your editor, terminal, and diff tool.

Installation

Every method installs the same wsx binary. Pick one.

Prerequisites:

  • git on your PATH.
  • The GitHub CLI (gh), optional, for pull request numbers and review marks on the dashboard.

Homebrew

macOS and Linux, Intel and ARM:

brew tap bakedbean/workspacex https://github.com/bakedbean/workspacex
brew install bakedbean/workspacex/wsx

Use the fully qualified name. A bare brew install wsx stops with a tap trust error.

Upgrade:

brew update && brew upgrade bakedbean/workspacex/wsx

cargo-binstall

cargo-binstall downloads the prebuilt binary, with no Rust build:

cargo binstall --git https://github.com/bakedbean/workspacex wsx

Nix

Run once without installing:

nix run github:bakedbean/workspacex

Install into your profile (Nix before 2.34 uses install instead of add):

nix profile add github:bakedbean/workspacex

Add to a NixOS or home-manager flake:

{
  inputs.wsx.url = "github:bakedbean/workspacex";
  # package: inputs.wsx.packages.${pkgs.stdenv.hostPlatform.system}.default
}

Prebuilt binaries

Download a tarball from the releases page, verify it against its .sha256 file, and install it:

f=wsx-<version>-<target>.tar.gz
echo "$(cat "$f.sha256")  $f" | shasum -a 256 -c -
tar xzf "$f"
sudo install -m 755 wsx-<version>-<target>/wsx /usr/local/bin/wsx
PlatformTarget
macOS, Apple siliconaarch64-apple-darwin
macOS, Intelx86_64-apple-darwin
Linux, ARM64aarch64-unknown-linux-gnu
Linux, x86-64x86_64-unknown-linux-gnu

The Linux binaries need glibc 2.35 or later.

From source

Requires Rust 1.88 or later.

git clone https://github.com/bakedbean/workspacex
cd workspacex
cargo install --path .

Quick start

This walkthrough takes you from install to a running agent session, then connects wsx to your editor, terminal, and diff tool.

  1. Install wsx. On macOS or Linux with Homebrew:

    brew tap bakedbean/workspacex https://github.com/bakedbean/workspacex
    brew install bakedbean/workspacex/wsx
    

    For other methods, see Installation.

  2. Register a repository:

    wsx repo add /path/to/your/repo
    
  3. Launch the dashboard:

    wsx
    
  4. Press n to create a workspace: a new branch and worktree with its own agent session. Press N (Shift-N) instead to start the agent in permissive mode, which skips permission prompts.

  5. Press enter to attach. The agent (Claude Code by default) runs inside the worktree.

  6. Press Ctrl-x d to detach back to the dashboard. The session keeps running.

  7. Tell wsx which tools the [e] editor, [t] terminal, and [v] diff keys launch. For example, Neovim in Alacritty:

    wsx config set editor_cmd   "alacritty --working-directory={path} -e nvim"
    wsx config set terminal_cmd "alacritty --working-directory={path}"
    wsx config set diff_cmd     "alacritty --working-directory={path} -e nvim -c 'DiffviewOpen {base}...HEAD'"
    

    {path} is the worktree and {base} is the diff base ref. Wrap terminal editors like nvim in a terminal command; GUI editors like code work directly. See Editor, terminal, and diff for placeholders and fallbacks.

Next steps

Dashboard

The dashboard is wsx's home screen: one row per workspace, grouped by repo, showing each agent's status, PR, and changes. Select a row and press enter to attach to its agent.

Keybindings

Every key in the wsx TUI, grouped by where you press it. Capital letters mean Shift plus the letter.

Dashboard

Moving around

KeyAction
j / k (or arrows)Move the selection
h / lFold / unfold the repo; l on a workspace attaches
z zToggle the repo's fold
z a (or z r)Unfold every repo
z M (or z m)Fold every repo
/Filter the rows; esc clears
escClear a status filter set by clicking the header tally
oSort by recency or by status (saved)
GGroup by repo or by attention
TabFocus the detail-bar reply, then the digest
qQuit; stops direct sessions (shared ones keep running)

q asks first if a workspace is still being created or archived.

Workspaces

KeyAction
enter / iAttach; on a repo header, create a workspace there
nCreate a workspace in the selected repo
NCreate one in permissive mode (skips agent permission prompts)
SCreate a tmux-shared workspace
TToggle the workspace between shared and direct
dArchive
e / t / v / gOpen in editor, terminal, diff, or lazygit
?Workspace actions card
#Edit tags
CPick the row's name color
KProcesses under the worktree
Ctrl-x 1–9Send a pinned command to the workspace
Ctrl-x aAgents panel

Repos and panes

KeyAction
K / J on a repoMove the repo up / down (saved)
sRepo settings
,Global settings
FHide rules for the repo
HRemote hosts
pToggle the digest
r (digest open)Refresh git and PR data
enter / l on a hidden groupShow its workspaces
h on a hidden groupCollapse it

Detail-bar reply

KeyAction
TabFocus the reply input
enterSend to the agent (starts it if needed)
escDiscard and return to the list

Switching panels

Panels open in a drawer at the bottom of the screen.

  • A panel's key opens it and replaces the open panel. Pressing the open panel's key again closes it. A key with nothing to open (? on a repo header) leaves the panel alone.
  • While a panel takes text (tag editor, name-color filter, name fields, a / filter), keys type. In the process list K kills; in the updates panel s splits.
  • Ctrl-x chords that open panels work from any panel. Progress cards for remote work ignore panel keys until they finish or you press esc.

Dialogs

KeyNew workspaceConfirm (archive, quit, share)
enterCreate (blank name generates one)—
y / n—Confirm / cancel
escCancelCancel
↑ / ↓Switch between name and tags—
TabCycle the agent—
Ctrl-sToggle shared—

Workspace actions card (?)

KeyAction
rRename the workspace and its branch
oOpen the setup log
xCancel a create in progress
C / #Name color / tags
j / kMove the selection, card stays open
e t v g enterAct on the workspace and close
? / escClose

Setup log viewer (? then o)

Shows the setup script's output: live while the workspace is being created, the saved log afterwards. Stderr lines are marked !. Only the last 2000 lines show.

KeyAction
j / k (or arrows)Scroll one line
PageUp / PageDownScroll ten lines
g / HomeJump to the start
G / EndJump to the end
esc / enterClose; setup keeps running

Panels opened from the dashboard

PanelKeys
Tag editor (#)type + enter add; arrows + space toggle; esc close
Hide rules (F)j/k move; space toggle; esc close
Name color (C)arrows move; type to filter; enter apply; Delete clear; esc close
Repo settings (s)j/k move; enter edit; d clear the repo's value; esc close
Global settings (,)j/k move; enter toggle, cycle, or edit; d reset to default; esc close
Processes (K)arrows move; r run a command; k SIGTERM; K SIGKILL; esc close
Remote hosts (H)j/k move; enter open; esc close
Remote workspace listenter attach or share; n/N new; r refresh; esc close

Digest (p, focused)

KeyAction
j / k (or arrows)Move the selection
enterAttach to the workspace
/Filter by name
rRefresh git and PR data
escClear the filter, or return focus
TabReturn focus to the list
p / qClose the digest

Attached view

Keys go to the agent, except chords after Ctrl-x. Pressing Ctrl-x alone shows a menu; pick with arrows and enter, or press the letter. wsx drops Ctrl-z and Ctrl-d so a slip can't suspend or exit the agent.

KeyAction
Ctrl-x dClose the pane; detach if it's the last one
Ctrl-x DSave the split layout, then detach
Ctrl-x arrowsMove focus between split panes
Ctrl-x uUpdates panel
Ctrl-x aAgents panel
Ctrl-x e / t / v / gEditor, terminal, diff, lazygit
Ctrl-x kProcesses
Ctrl-x <Prompt tag picker
Ctrl-x #Workspace tags editor
Ctrl-x RWorkspace recap (goal / state / next); j/k scroll; esc closes
Ctrl-x 1–9Send a pinned command
Ctrl-x xSend a literal Ctrl-x to the agent
Ctrl-x escDismiss the menu
Ctrl-x q w r y i o p s h jSwitch the pane to another agent (multi-agent workspaces)

A workspace attached from a remote host takes only Ctrl-x d, Ctrl-x <, Ctrl-x #, Ctrl-x x, and Ctrl-x 1–9; Ctrl-x alone shows a menu of those.

Panels opened from the attached view

PanelKeys
Updates (Ctrl-x u)j/k move; enter/l attach, or expand a hidden group; h fold a hidden group; v side-by-side split; s stacked split; o/G sort and group; / filter; esc close
Agents (Ctrl-x a)j/k move; enter add the selected kind; a add one of each; x remove the newest; esc close
Prompt tag (Ctrl-x <)arrows move; 1–9 pick; type to filter or name a new tag; enter write the body; Ctrl-d delete the tag; esc close
Prompt tag bodyCtrl-s insert; esc back

Status indicators

Each dashboard row fits a workspace's agent status, PR, processes, and changes on one line, so you can see which workspace needs you without attaching.

Row columns

Left to right:

ColumnShows
Agent stripOne colored bar per agent, primary rightmost
StatusA colored bar and a status glyph (see below)
Branch⎇ colored by PR state, the name, tags, and badges
PR⏺ #241 open plus a review mark
Processes● 2p for two tracked processes, · for none
Diff+N −N lines committed on the branch since its base
MessageStatus word, then the agent's recap or its latest activity
AgeTime since the last activity

The name is bold, and warn-colored for a workspace created in permissive mode unless you picked a name color with C. In the message column, ▸ means the agent reported its status with wsx status and └ means wsx inferred it.

Status glyphs

GlyphWordMeaning
?askingThe agent asked a question or waits on a permission prompt
!stalledNo output for 60 seconds mid-turn
spinnerthinkingThe agent is working
spinnerwaitingThe session is running but quiet for over 30 seconds
✓doneThe agent finished its turn
·idleNo session, or a session you haven't prompted yet

The dashboard's top line tallies how many workspaces hold each status, listing only the ones in use: ? 2 question ⠋ 3 thinking · 4 idle. The tally counts every workspace, including ones a filter hides. A theme whose header leaves out $statuses shows the tally on its own row below the header instead, with every status listed, zeros dimmed. That row isn't clickable.

Filtering by status

Click a status in the header tally to show only the workspaces in that status. Clicking ? 2 question lists just those two rows. While the filter is on, that status is highlighted and the others are dimmed. If the last workspace leaves that status, its item stays in the tally at 0, so you can still see what's filtering the list and click to clear it. The filter lasts until you clear it or quit wsx.

  • Click the same status again, or press esc, to clear the filter.
  • Click a different status to switch to it. Only one status filters at a time.
  • A status filter combines with a / filter: both must match. With both on, the first esc clears the typed filter and the second clears the status.
  • Like a / filter, a status filter shows rows that hide rules would otherwise hide, so the list matches the tally.

In a workspace with several agents, a peer's bar in the agent strip turns into a spinner while that agent prints output. A + stands in for peers that don't fit.

Branch badges

BadgeMeaning
spinnerBeing created or archived
⚙!Setup script failed; the workspace still works. ? then o shows the log
⚙?Setup was cancelled
✗The worktree was never created. Archive it with d
✉!A message to the agent couldn't be delivered
◇tmux-shared: green while its session runs, red when it doesn't
columns iconA saved split layout (nerd fonts only)

PR chip

The chip and the ⎇ glyph take the PR state's color: no PR, draft, open, conflicts, merged, or closed. Open PRs also carry a review mark:

MarkMeaning
✓Approved
✗Changes requested; a number counts unresolved threads
◌Review required

PR data needs gh, installed and authenticated.

Alerts

When an agent asks a question, waits on a permission prompt, finishes, or stalls, wsx rings the terminal bell. The workspace also moves to the front of the attached view's workspace strip until you attach to it.

  • Each state has its own bell pattern, set with the notification_bell_* settings.
  • wsx config set notifications off turns off the bell and the flag.
  • Workspaces that were already waiting when wsx started don't ring.
  • While wsx runs, the bell doesn't ask the window manager for urgency, so on Hyprland it won't switch your desktop workspace.

See Settings for the allowed values.

Detail bar

The detail bar runs along the bottom of the dashboard and shows more about the selected workspace: its recap, recent chat, processes, and changed files. It ends in a reply input, so you can answer an agent without attaching.

Layout

The bar holds 1–4 equal-width columns, called containers. Each container stacks one or more modules:

ModuleShows
session_summaryThe recap (goal, state, next), tool counts, status, model
recent_chatThe latest messages between you and the agent
processesTracked processes in the worktree
recent_filesFiles the agent edited recently, with line counts

session_summary falls back to the session's first prompt when the agent hasn't written a recap. Scroll a container with the mouse wheel.

Replying

Press Tab to focus the reply input, type, and press enter to send it to the agent. wsx starts the agent first if it isn't running. esc discards the draft.

Configuring

The detail_bar_config setting holds a JSON value. Every field is optional, and missing fields take the defaults:

{
  "visible": true,
  "height": { "percent": 30, "min_rows": 8, "max_rows": 18 },
  "containers": [["session_summary"], ["recent_chat"], ["processes", "recent_files"]],
  "bottom_rule": true
}
FieldDefaultEffect
visibletruefalse hides the bar and its reply input
height.percent30Height as a percent of the terminal, 5–80
height.min_rows8Minimum height in rows, 4–40
height.max_rows18Maximum height in rows, 4–60
containersas aboveUp to 4 columns of module IDs; [] leaves a column empty
bottom_ruletrueDraw a rule under the reply row; false saves a row

When every container is empty ([[], [], []]), the bar shrinks to its header and reply input. An unknown module ID shows a [unknown: <id>] placeholder.

Set or edit the global value:

wsx config edit detail_bar_config          # open in $EDITOR
wsx config set detail_bar_config ""        # back to the defaults

Examples:

# Taller bar on a big monitor
wsx config set detail_bar_config '{"height": {"percent": 45, "max_rows": 24}}'

# One full-width chat column
wsx config set detail_bar_config '{"containers": [["recent_chat"]]}'

# Hide the bar
wsx config set detail_bar_config '{"visible": false}'

wsx config set rejects malformed JSON and keeps the old value. Out-of-range heights are clamped.

Per-repo override

To change the bar for one repo, press s on the dashboard, select detail_bar_config, and press enter. $EDITOR opens on the repo's override. Write only the fields you want to change, for example { "visible": false }. Press d on the row to clear the override.

visible, height, and bottom_rule override field by field. containers replaces the global list.

Project manager digest

The digest is a pane below the workspace list with a card for every ready workspace, grouped by repo. Use it to review what each agent is doing and what's left without attaching to each one.

Press p to open it. It opens focused and needs no setup.

Within each repo, cards are ordered blocked first, then waiting, then the rest, oldest activity first.

What a card shows

  • Header: workspace name, branch, agent, and the agent's reported status, such as [blocked 4s], with its message. With several agents, it shows the most urgent status.
  • Recap: the agent's goal:, state:, and next: lines. A workspace without a recap shows no recap yet.
  • Facts: git counts (↑ahead ↓behind ~modified ?untracked), the PR chip, active <age> ago, and recap stale when the agent has been active since its last recap.

Agents write recaps and statuses with wsx recap and wsx status. See Status and recap.

Keys

KeyAction
pOpen or close the digest
j / k (or arrows)Move the selection
enterAttach to the workspace
/Filter cards by workspace name
escClear the filter, or return focus to the list
TabCycle focus: list, reply input, digest
rRefresh git and PR data
qClose the digest

q closes the digest only while the digest has focus. With the list focused, q quits wsx and stops its direct (non-shared) sessions.

Tags and hiding

Tags are short labels on workspaces, like parked or spike. Use them, and each PR's state, to search the dashboard and to hide workspaces you're not working on.

Tagging workspaces

Press # on a workspace, or Ctrl-x # while attached to one, to open the tag editor:

KeyAction
type + enterAdd the tag
arrows + spaceToggle a listed tag
escClose

Typing filters the list, which offers every tag used in wsx. Changes save as you make them.

Attached to a workspace on a shared host, Ctrl-x # edits its tags on that host. The editor offers the tags used by that host's listed workspaces. Each change shows at once and runs wsx workspace tag there over ssh, one at a time in the order you made them; they finish even if you close the editor or detach. If a change fails, the editor shows the host's actual tags and why it failed.

To tag a workspace as you create it, press ↓ in the new-workspace form and type its tags, separated by spaces or commas. An invalid tag keeps the form open and says which one. Creating a workspace with the cursor on a hidden tag group, or on a workspace inside one, fills in that group's tag; edit the field to change it.

Tag rules:

  • Lowercase a-z, 0-9, -, and _, starting with a letter or digit.
  • Up to 32 characters.

From a shell or an agent:

wsx workspace tag add parked
wsx workspace tag rm parked --workspace backend/old-spike
wsx workspace tag list --all

See wsx workspace tag for the full syntax.

Tags show dimmed after the branch name: ⎇ wsx/my-slug #parked #ui. They use only the space left in the branch column, and tags that don't fit collapse to a count like +2. To make room, widen the column with the dashboard_branch_width setting. Attached to a workspace, local or remote, its tags show after its name in the top bar; when the bar is too narrow they drop as a group rather than folding to a count.

Workspace tags are unrelated to prompt tags.

Hiding workspaces

Press F on a repo header, or any row in the repo, to choose what that repo hides. Move with j/k and toggle with space. wsx saves the choice per repo.

Hidden workspaces collapse into one dimmed row per rule:

▸ ⏺ merged  ·  4 hidden
▸ #parked  ·  2 hidden

Press enter or l on that row to show its workspaces, and h to collapse it. An expanded group and its workspaces show at full brightness. Expanded rows stay open until wsx restarts.

By PR state

The states are no PR, draft, open, conflicted, merged, and closed. Each hides every matching workspace, except no PR, which keeps a workspace visible when any of these is true:

  • It has commits ahead of its base branch, or uncommitted changes.
  • An agent session is running, or its agent reports working, waiting, or blocked.
  • It was created, or its agent was active, in the last 3 days.

A workspace whose PR state hasn't loaded yet stays visible.

By tag

The F picker lists every tag in use above the PR states. A tag rule hides every workspace with that tag, even one with work in progress. A workspace matching a tag rule and a PR rule is grouped under the tag. A rule for a tag no longer in use stays in the picker with a count of 0, so you can turn it off.

What still counts hidden workspaces

  • A / search covers every workspace, hidden or not.
  • Status bar, Waybar, and menu bar counts include hidden workspaces.
  • The updates panel (Ctrl-x u) shows the same group rows as the dashboard. Expanding a group in either place expands it in both.
  • The attached view's top bar leaves them out, except workspaces carrying a tag listed in the top_bar_show_tags setting. Those stay in the bar unless a PR rule hides them.
  • The by-attention view (G) lists them in a HIDDEN section.

If the selected workspace becomes hidden, for example when its PR merges, the selection moves to its group's row.

Searching

Press / and type. The filter matches branch names, repo names, tags, status words, and recaps, ignoring case. Prefix a tag with # to match only that exact tag:

/parked      anything containing "parked"
/#parked     tagged exactly "parked"

esc clears the filter.

To narrow the list by status, click a status in the header tally. See Filtering by status.

Process tracking

wsx lists the long-running processes working inside each workspace's worktree, such as dev servers and watchers. Use it to find and stop what a workspace left running, or to start a command there without opening a terminal.

A workspace with tracked processes shows a count like ● 2p on its dashboard row. Press K on the row, Ctrl-x k while attached, or click the count to open the list:

  PID    COMMAND          CWD
  12345  npm              /home/user/wt/fix-bug
  12389  pytest           /home/user/wt/fix-bug/tests
[↑/↓] move   [r] run   [k] term   [K] kill   [esc] close
KeyAction
↑ / ↓Move the selection
kSend SIGTERM to the process
KSend SIGKILL to the process
rRun a command in the worktree
escClose

j and k don't move the selection here, because k stops a process.

Running a command

r runs a command with sh -c in the worktree, in the background. Output goes to a log file under ~/.local/state/wsx/logs/, and wsx shows the path. The command keeps running after you quit wsx, and it appears in the list so you can stop it with K.

What's listed

  • Processes whose working directory is inside the worktree.
  • Not shells and editors (bash, zsh, nvim, code, and so on).
  • Not helpers that Claude Code and editors start, such as MCP and language servers, unless they listen on a TCP port. A dev server started from inside Claude Code still shows.

Archiving stops processes

Archiving a workspace, with d or wsx workspace archive, stops every listed process before it removes the worktree. Each gets SIGTERM, then SIGKILL after two seconds. --keep-worktree skips this. A process wsx can't signal, such as one owned by another user, is reported and the archive continues.

Requirements

Process tracking needs lsof. Without it, the count stays at 0 and the list is empty.

Mouse and text selection

wsx captures the mouse so the wheel scrolls the agent's history and parts of the screen are clickable. As a result, a plain click-and-drag no longer selects text.

Selecting text

Hold Shift while you drag. Most terminals (Alacritty, Kitty, WezTerm, iTerm2, GNOME Terminal) pass a Shift-drag through to their own selection. On macOS, some terminals use Option instead. In iTerm2 you can also right-click and choose Bypass mouse reporting.

Scrolling

  • The wheel scrolls the attached pane's history. If the agent handles the mouse itself, the wheel goes to the agent; hold Shift to scroll wsx's history instead.
  • On the dashboard, the wheel over a detail bar container scrolls that container.

Clickable targets

ClickDoes
A key hint in the footerPresses that key
A status in the header tally (? 2 question)Filters the list to that status; click it again, or press esc, to clear
A row's PR chip (⏺ #123 open)Opens the PR in your browser
A repo header's PR linkOpens your open PRs in that repo on GitHub
A row's process count (● 2p)Opens the process list
A pinned-command chipSends that pinned command
A prompt-tag chipOpens that prompt tag's body box
A workspace in the attached view's stripAttaches to it
The strip's +N moreOpens the updates panel
An agent pillSwitches the pane to that agent
A $hosts chipOpens that host's shared workspaces
The footer activity graphPicks the graph's time window

The repo PR link shows as PR, or as a pull-request icon with nerd_fonts on. It turns green when one of the repo's workspaces has an open PR. It appears only for repos whose origin is on github.com.

Opening PRs needs gh, installed and authenticated.

Attached view

Press Enter on a dashboard row to attach to a workspace's agent full-screen. Commands in the attached view start with the Ctrl-x leader. Press Ctrl-x d to detach back to the dashboard. The agent keeps running.

To send a literal Ctrl-x to the agent, press Ctrl-x x. Keybindings lists every attached-view key.

Updates panel and split panes

While you're attached to one workspace, wsx keeps the others in view. Use the top-bar strip and the updates panel to jump between workspaces, and split the view to watch several agents at once.

Workspace strip

The top bar lists every other workspace after the focused one's label, as <glyph> <repo>/<name> (<age>):

  • The glyph and color match the workspace's dashboard row.
  • Workspaces that need attention come first. The rest follow the dashboard's order.
  • Workspaces the dashboard hides are left out, even ones that need attention. To keep a hidden tag in the strip, list it in the top_bar_show_tags setting. A workspace with a listed tag ignores every tag rule, but PR rules still hide it.
  • Click an entry to switch to that workspace.
  • When the strip runs out of room it ends in … +N more. Click it to open the updates panel.

Updates panel

Press Ctrl-x u to open the updates panel, a compact dashboard in the bottom drawer. It lists every visible workspace in the dashboard's order and grouping, with nothing folded. Hidden workspaces fold into the dashboard's group rows: press l on a group to list its workspaces, h to fold it again, or enter to toggle it. A / search covers hidden workspaces too. Each row shows the workspace's state, latest event, PR chip, and +N −N diff. It updates live.

KeyAction
↑ / ↓, k / jMove the selection (wraps at the ends)
Enter or lSwitch the current pane to the selected workspace
l / hExpand / fold a hidden group (Enter toggles it)
vOpen the selected workspace in a vertical split (side by side)
sOpen the selected workspace in a horizontal split (stacked)
oCycle the sort mode (recency ↔ status), same as the dashboard
GToggle grouping (by repo ↔ by attention)
/Filter by workspace name, repo, or status text
EscClear the filter, or close the panel

While a filter is active, letter keys type into the filter. Use the arrow keys and Enter to pick a row.

Split panes

You can tile several workspaces in the attached view, vim-style. Any pane can split again, so you can build any tree of vertical and horizontal splits. Each pane has a one-line title bar, and ● marks the focused pane, which receives your keystrokes.

  1. Attach to a workspace and press Ctrl-x u.
  2. Select another workspace and press v or s. Focus moves to the new pane.
  3. Repeat to add more panes.
KeyAction
Ctrl-x ←/→/↑/↓Move focus to the neighboring pane
Ctrl-x dClose the focused pane. Closing the last pane detaches
Ctrl-x Shift-DSave the layout and detach to the dashboard

Other panes keep running when you close one.

Saving a layout

Ctrl-x d doesn't remember the arrangement. Press Ctrl-x Shift-D to save the split tree and the focused pane against the first workspace you attached to, then detach.

  • The next time you attach to that workspace, wsx restores the layout and restarts the other panes' sessions.
  • Panes whose workspaces no longer exist are dropped. If none remain, you get a single pane.
  • A workspace with a saved layout shows a columns glyph next to its branch on the dashboard (Nerd Fonts only).

Pinned commands

Pinned commands put the prompts you send often, such as /pull-request, on a chip row below the agent pane. Fire one with a key instead of typing it.

[1] PR   [2] FB   [3] /loop /baby…   [4] UR
KeyAction
Ctrl-x 1 … Ctrl-x 9Fire chip 1–9
Click a chipFire that chip

A chip types its command into the agent and presses Enter. Ctrl-x <digit> also works on the dashboard, where it sends the command to the selected workspace's agent and starts its session if needed.

Configure chips

Set the pinned_commands setting, one entry per line:

PR=/pull-request
FB=/feedback
/loop /babysit-prs
review=/agent-review ...
  • Label=command shows the label on the chip. A bare line shows the command itself.
  • Labels longer than 14 columns are truncated.
  • Only the first 9 chips get keys. At narrow widths trailing chips are hidden, but their keys still work.
wsx config edit pinned_commands               # edit in $EDITOR
wsx config set pinned_commands @./pinned.txt  # load from a file
wsx config set pinned_commands ""             # clear

wsx config set replaces the whole list. Use wsx config edit to add one chip. A repo's own list, set with wsx repo set-pinned-commands, replaces the global list for that repo. See Settings.

Chips that type without submitting

End a command with ... or … to type it without pressing Enter. Use this for a command that takes an argument you choose each time:

review=/agent-review ...
  • Attached, the chip types /agent-review and leaves the cursor there.
  • On the dashboard, the text goes into the detail bar's reply input. Finish it there and press Enter.
  • A command that really ends in ... can't be pinned as a submitting chip.

Some commands ask for missing input on their own. The bundled handoff skill asks what to implement, and agent-review asks which reviewer to spawn.

Prompt tags

Prompt tags wrap a section of your prompt in XML tags, such as <context>…</context> or <task>…</task>, so the model can tell the parts apart. Anthropic's prompting guidance recommends this.

Insert a tag

Press Ctrl-x < or click the <> chip in the footer to open the picker:

 name: ▏
  1  context                   ×12
  2  task                      ×7
  3  constraints               ×2
 [↑/↓] move   [enter] body   [1-9] pick   [^d] delete   [esc] close
KeyAction
TypeFilter tags by prefix
EnterOpen the selected tag's body box, or create a new tag
1–9Pick a listed tag (while the name field is empty)
Ctrl-dDelete the selected tag
EscClose the picker

In the body box, type your text. Enter inserts a newline.

KeyAction
Ctrl-sInsert the wrapped text into the agent's prompt
EscGo back to the picker and keep your draft

Ctrl-s inserts this into the agent's prompt without submitting it:

<context>
…your text…
</context>

Stack several sections, add an instruction, and press Enter yourself.

  • Each insert raises the tag's use count. The three most-used tags appear as footer chips. Click one to open its body box.
  • If wsx can't confirm the insert, for example because the agent has exited, the body box stays open with your draft.
  • Tag names must match [A-Za-z_][A-Za-z0-9_.-]*.

Configure

The tags and their use counts live in the prompt_tags setting, one name=uses per line:

wsx config edit prompt_tags     # edit in $EDITOR
wsx config set prompt_tags ""   # clear

Lines that don't parse are dropped.

The footer chips are the $tags bar segment. See Themes to move or restyle them. Ctrl-x < works even if your theme leaves the segment out. If your own ~/.config/wsx/theme.toml sets [attached_bottom].format, add ($tags ) to it and a [tags] table to show the chips:

[attached_bottom]
format = "$keys  ($pins  )($tags  )"

[tags]
format      = "[<$label>]()"
separator   = "  "
more_format = "[ <> ](bold)"

Agents

wsx runs a coding agent in each workspace and keeps track of what it's doing.

Coding agents

wsx runs Claude Code in each workspace by default. You can pick a different coding agent for all new workspaces or for one workspace.

wsx config set coding_agent codex            # default for new workspaces
wsx workspace create backend --agent pi      # this workspace only

A workspace created from inside another workspace inherits its agent kind. See wsx workspace create.

AgentProjectIts own config
claude (default)Claude Code~/.claude.json
pi@earendil-works/pi-coding-agent~/.pi/
hermeshermes-agent~/.hermes/config.yaml
codexOpenAI Codex~/.codex/config.toml
ompoh-my-pi (@oh-my-pi/pi-coding-agent)~/.omp/agent/config.yml

wsx runs the binary of the same name from PATH. Override the path with WSX_<AGENT>_BIN, and the model with the agent's model variables. See Environment variables.

Capabilities

Capabilityclaudepihermescodexomp
wsx instructions and auto-rename✓✓✓✓✓
Related repos as extra dirs✓–––✓
Automatic status from the harness✓––✓–
Each agent resumes its own session✓✓–✓✓
Yolo mode (--yolo)✓–✓✓✓
MCP inheritance✓––––
Remote control✓––––
~/.claude/skills and ~/.claude/commands✓skills–commands✓

claude

  • Instructions go in --append-system-prompt. Related repos go in --add-dir.
  • wsx adds status hooks through --settings.
  • Yolo passes --dangerously-skip-permissions.
  • Remote control is on by default. Turn it off with the remote_control setting.

pi

  • Instructions go in --append-system-prompt.
  • wsx loads a small extension (pi -e) that reports each session start, so each pi agent resumes its own session.
  • WSX_PI_MODEL and WSX_PI_PROVIDER pick the model for new sessions.
  • pi reads skills from ~/.claude/skills.

hermes

  • Hermes has no flag for extra instructions, so wsx writes them into a <!-- BEGIN wsx-managed --> block at the end of the worktree's AGENTS.md on each start.
    • When wsx creates AGENTS.md, it copies the repo's CLAUDE.md into it.
    • An untracked AGENTS.md is added to .git/info/exclude.
    • A tracked AGENTS.md shows as modified while the block is present.
  • On resume, wsx picks the newest Hermes session started since the workspace's first Hermes spawn. If two worktrees start Hermes within a few seconds, it can pick the wrong one.
  • Added Hermes agents always start fresh.
  • WSX_HERMES_MODEL overrides the model in ~/.hermes/config.yaml. WSX_HERMES_PROVIDER is passed as --provider, which Hermes's default REPL mode may ignore; set the provider in config.yaml to be sure.
  • The detail bar shows chat and tool counts, but not edited files.

codex

  • Requires Codex 0.146.0 or newer.
  • Instructions go in -c developer_instructions=…. Nothing is written to the worktree. Codex reads CLAUDE.md when the repo has no AGENTS.md.
  • Instructions are set only on a fresh start. A resumed session keeps the ones it started with, so edits to custom instructions or related repos don't reach it.
  • Status comes from Codex's notify after each turn.
  • Your ~/.claude/commands/*.md are mirrored into a Codex plugin on each start, so commands such as /pull-request work in Codex.
  • wsx turns off Codex's startup update prompt, which would block input. Update Codex yourself, for example with npm install -g @openai/codex.
  • Yolo passes --dangerously-bypass-approvals-and-sandbox.
  • The detail bar doesn't show edited files.

omp

omp and pi are separate harnesses with different binaries. You can install both.

  • Instructions go in --append-system-prompt. Related repos go in --add-dir.
  • Yolo passes --approval-mode yolo. Otherwise omp uses your tools.approvalMode.
  • WSX_OMP_MODEL sets the model. It accepts provider/id.
  • On each start wsx passes --config <state dir>/omp-config.yml. This overlay turns on omp's ~/.claude skills, commands and Claude plugins, which omp 18 turns off by default.
    • It overrides your own settings for those keys in wsx sessions, and replaces your enabledProviders list.
    • It enables every skill and command under ~/.claude, not only wsx's.
    • Your ~/.omp/agent/config.yml is never edited.
  • Pinned chips that name a bundled skill are sent as /skill:<name>. See Agent skills.

Multi-agent workspaces

A workspace can run several agents, of the same kind or different kinds. They share one worktree and branch, each has its own session, and they can message each other. Use this to run a reviewer next to the agent doing the work, or to try claude and codex on the same problem.

Every workspace starts with one primary agent, chosen by --agent or the coding_agent setting. See Coding agents. The primary lives as long as the workspace.

Agents write to the same files. Hand work off with messages rather than editing the same paths at once.

Labels

Each agent's label is its kind, plus #N for the second and later agents of that kind: claude, claude#2, codex. Labels appear in the footer, in wsx agent list, and in message banners. The label primary always means the workspace's primary agent.

wsx sets two variables in each agent's session, which the wsx agent commands read:

VariableValue
WSX_WORKSPACE_IDThe agent's workspace
WSX_AGENT_INSTANCE_IDThis agent instance

Without WSX_WORKSPACE_ID, wsx finds the workspace from the current directory, so the commands also work from a shell in the worktree.

Add and remove agents

Press Ctrl-x a on the dashboard or in the attached view to open the agents panel. It lists the workspace's agents and a picker of the five kinds.

KeyAction
↑ / ↓, k / jMove through the picker
EnterAdd the selected kind
aAdd one agent of every kind
xRemove the most recently added non-primary agent
EscClose the panel

A new agent starts at once with the workspace's context. When you remove an agent you're viewing, the pane switches to the primary.

From the CLI:

wsx agent add codex        # prints: added codex
wsx agent remove claude#2  # can't remove the primary

Removing an agent discards messages still queued for it. See wsx agent add and wsx agent remove.

Switch focus between agents

With two or more agents, the attached view's chip row shows an agent pill for each, with a switch key:

 ^x  menu   1 pr   2 fb  ────────   ● claude q   ○ codex w   ○ pi r    opus 4.8 45k/200k
  • Press Ctrl-x and the pill's key, or click the pill, to show that agent in the focused pane.
  • Keys come from q w r y i o p s h j, primary first. Agents past the tenth have no key but stay clickable.
  • ● marks the agent in the focused pane. A spinner replaces the dot while an agent works.
  • The model and token count follow the agent in the focused pane.
  • On a narrow terminal the pills hide as a group. Their keys still work.

Messages

Agents send each other messages with wsx agent send. The target is a label, primary, or a numeric instance id:

wsx agent send codex "review the parser changes"
wsx agent send primary --file brief.md
wsx agent send --workspace backend/add-widgets primary "start on the API"

send prints the message id, for example queued message #561 to codex (2747 bytes). The dashboard delivers the message into the target's session with a banner that says how to answer:

[message #561 from claude#2; reply with: wsx agent reply 561 <message>]
…message body…

A sender in another workspace shows as <repo>/<slug> <label>. A message from a plain shell has the banner [message #561].

  • Delivery waits until the target is idle at its prompt. If the target isn't running, wsx starts it.
  • Only a running dashboard delivers messages. Without one, send warns and the message waits in the queue.
  • send refuses a target whose binary isn't installed.
  • A message that fails to deliver several times shows a red ✉! badge on the dashboard row. Restart wsx to retry.
  • Delivery is at least once. A wsx crash mid-delivery can repeat a message.

To answer, read mail, or wait for a reply:

wsx agent reply 561 "tests pass"          # reply to message #561
wsx agent messages                        # your inbox
wsx agent wait --after 561 --done codex   # block until codex replies or reports done
wsx agent whoami                          # your label, instance id, and workspace

wsx agent wait only reads a message. The dashboard still delivers it later, so the agent sees the same [message #561 …] banner again and should treat it as a duplicate.

The wsx agent reference covers every flag: send, reply, messages, wait, and whoami.

List agents

wsx agent list
1  claude  (primary)  working — "running the test suite" (model, 3m ago)
2  claude#2  done (hook, 10m ago)
4  codex

The number is the instance id. Each agent's own status follows its label. See Per-agent status and wsx agent list.

Sessions survive a restart

When you quit and restart wsx, each agent resumes its own conversation, even when several agents of one kind share the worktree.

AgentResumes with
claudeclaude --resume <id>, with the id from wsx's status hooks. Follows /clear.
codexcodex resume <id>, after the agent's first turn ends
pipi --session-id <id>. Follows /new, /resume, and forks.
ompomp --resume=<file>. Follows /new.
hermesThe primary resumes its newest session. Added agents start fresh.
  • Until wsx has an agent's session id, the primary uses its harness's "continue latest" and an added agent starts fresh.
  • If an agent's recorded session no longer exists on disk, the agent starts fresh.
  • Changing a workspace's agent kind forgets its sessions.
  • wsx looks only in each harness's default session directory. A custom CODEX_HOME or CLAUDE_CONFIG_DIR isn't supported.

Inspect other workspaces

The read commands take --workspace <repo>/<slug>, so an agent can check on a workspace it handed work to:

wsx status show  --workspace backend/add-widgets
wsx recap show   --workspace backend/add-widgets
wsx agent list   --workspace backend/add-widgets
wsx context show --workspace backend/add-widgets

For every workspace at once, use wsx workspace list --json.

Status and recap

Agents tell wsx what they're doing so you can see at a glance which workspaces need you. The status is a state word and a one-line message. The recap is the task's goal, current state, and next step.

Status states

StateMeaning
workingThe agent is doing substantive work
waitingThe agent is parked on something external, such as CI
blockedThe agent needs a decision or answer from you
doneThe task is finished
busyThe turn ended, but background work is still running (hooks only)

Set status

An agent sets its own status:

wsx status set blocked --message "need your call on the schema"

wsx status set also accepts the recap flags, so one call can update both:

wsx status set working --message "fixing tests" --state "2 tests failing" --next "fix regex"
CommandEffect
wsx status showPrint the workspace's status and each agent's (bare wsx status too)
wsx status clearFrom an agent, clear its own status. From a shell, clear every agent's

See wsx status set and wsx status show for all flags.

Automatic status from hooks

Claude and Codex report status without the agent's help. wsx wires their lifecycle events to wsx status from-hook (Claude) or Codex's notify.

EventStatus
You submit a prompt (Claude)working
Permission prompt, AskUserQuestion, or plan approval (Claude)blocked
Claude's idle notificationwaiting
Turn ends with a question (ends in ?)blocked
Turn ends while subagents or shell tasks run (Claude)busy
Turn ends otherwisedone

A status the agent sets itself holds through the end of that turn:

  • The turn-end hook keeps an agent's own done, blocked, or waiting and its message. The next turn that ends without a new wsx status set replaces it.
  • The idle notification never overrides an agent's own status.
  • New work (working or busy) replaces it and drops the old message.
  • If the agent said working and the turn ends, the hook's result replaces it.

A background monitor, such as the watcher Claude keeps after publishing an artifact, doesn't count as background work. A turn that ends with only a monitor running reads as done.

pi, Hermes, and omp have no status hooks. They rely on wsx status set, and the dashboard reads their session logs.

Per-agent status

Each agent's status is recorded separately, keyed by its WSX_AGENT_INSTANCE_ID. A status set from a plain shell or an editor-hosted agent goes to the primary. wsx agent list and wsx status show print each agent's status:

status: blocked — "need your call on the schema" (model, 1m ago)
agents:
  claude (primary): working — "implementing" (model, 4m ago)
  claude#2: blocked — "need your call on the schema" (model, 1m ago)

The workspace status comes from its agents. The state that most needs you wins, in this order: blocked, working, busy, waiting, done. The most recent breaks ties. So one agent finishing can't mark the workspace done while another still works.

  • The dashboard and project manager digest combine the workspace status with the primary agent's session. A peer's older status gives way to newer activity in the primary's transcript.
  • On the dashboard, a reported status holds until the agent's session log shows newer activity. Then wsx reads the log instead. busy holds until the next hook.
  • A status never expires. An agent whose session died keeps its last status until it reports again, is cleared, or is removed.
  • Removing an agent drops its status.

Recap

The recap is three one-line fields, each with a short form for narrow views:

FieldShort formHolds
goal--goal-shortWhat the task is for (short form ≤40 chars)
state--state-shortWhere the work stands (short form ≤24 chars)
next--next-shortThe next step (short form ≤24 chars)
wsx recap set --goal "Audit V2 invoices auto-issued today for the CV-04964 drift bug" \
              --goal-short "Audit V2 invoices, CV-04964"
wsx recap set --state "tests failing" --next "debug the regex"

wsx recap set updates only the fields you pass. Write short forms in telegraphic style: identifiers and ticket numbers, no articles. wsx recap show prints the recap, and wsx recap clear deletes it. See wsx recap set.

Where they show up

PlaceShows
Dashboard rowStatus word, then the recap's short forms
Detail bar SESSION SUMMARYFull recap, then status
Project manager digestFull recap and status for every workspace
Waybar, Plasma, and menu barWorkspace status. The menu bar also shows recaps

When a recap field has no short form, the dashboard row shows the full field with articles removed.

What agents are told

wsx's process doctrine, which it adds to every agent's instructions, tells agents to:

  • set status as they go: working when they start, blocked when they need you, waiting when parked, done when finished;
  • set the recap goal once they understand the task, and update state and next whenever they set status or end a turn unfinished;
  • keep the short forms filled in.

Change or turn off the doctrine with the process_doctrine and process_doctrine_extra settings. See Settings.

Agent skills

wsx bundles skills that teach agents to drive wsx: creating workspaces, messaging peers, and handing work off. Install them once per machine.

wsx setup install-skill

This writes every bundled skill to each agent's skills directory:

AgentDirectoryWhen
claude, pi, omp~/.claude/skills/Always
codex~/.codex/skills/codex is on PATH, WSX_CODEX_BIN is set, or ~/.codex exists
hermes~/.hermes/skills/hermes is on PATH, WSX_HERMES_BIN is set, or ~/.hermes exists

Re-run it after upgrading wsx. It reports already up to date for unchanged skills and overwrites any copy that differs, including your local edits. omp reads ~/.claude/skills through a config overlay wsx passes. See Coding agents.

Bundled skills

SkillUse it to
wsxDrive the wsx CLI: workspaces, naming, peer agents, and work across related repos
agent-reviewStart a peer agent that reviews the current branch against main and reports back to you
handoffContinue a finished workspace's feature in a fresh workspace

agent-review takes the reviewer kind (claude, pi, hermes, codex, or omp) and asks if you omit it. It adds the reviewer with wsx agent add and gives it the branch diff. The reviewer runs the repo's own review skill when it has one (any *review* skill under .claude/skills/, .agents/skills/, or .github/skills/, such as pr-review), else its harness's built-in, then replies with its findings.

handoff takes what the new workspace should implement, or asks for it:

/handoff add a --json flag

It creates a workspace in the same repo and briefs its agent with a summary of this session: decisions, rejected approaches, gotchas, file pointers, and follow-ups. Reply defer to have the new agent wait for your request. The old agent marks itself done and doesn't archive its workspace.

Pinning skills as chips

Add skills to your pinned commands with wsx config edit pinned_commands:

agent-review=/agent-review ...
handoff=/handoff
  • The ... makes the agent-review chip type /agent-review and wait for you to add the reviewer kind.
  • The handoff chip runs with no request, so the agent asks what to implement.
  • In omp, wsx sends a chip naming a bundled skill as /skill:<name>, because omp's own /handoff would catch it. Pin the plain /handoff form. Typing /handoff yourself in omp still runs omp's built-in command.

Configuration

Configure wsx with global and per-repo settings, setup and archive scripts, themes, and auto-rename.

Settings

Global settings live in the wsx state database. Read and change them with wsx config. Per-repo values override some of them.

wsx config list
wsx config get <key>
wsx config set <key> <value>
wsx config edit <key>        # opens $EDITOR (default: vi)

Value sources:

  • A literal: wsx config set branch_prefix bakedbean
  • A file, prefixed with @: wsx config set custom_instructions @./instructions.md
  • An empty string clears the key and restores the default: wsx config set custom_instructions ""
  • wsx config edit <key> opens the current value in $EDITOR. Use it for multi-line values.

config set rejects unknown keys. On/off keys accept on/true/1/yes and off/false/0/no. See wsx config for the full syntax.

Global settings drawer

Press , on the dashboard to browse every key below without leaving wsx. The footer describes the selected key and lists its allowed values.

  • Unset keys are dimmed and show their default.
  • ↑/↓ selects a key. Enter flips an on/off key, steps a fixed-choice key (theme, coding_agent, bells, sort mode, usage window) to its next value, or opens any other key in $EDITOR.
  • d clears the key, restoring its default. Esc closes the drawer.

Values are validated the same way as wsx config set. Dashboard sort, pin, and top-bar tag settings apply at once; keys marked "Restart wsx to apply" still need a restart.

General

KeyDefaultEffect
branch_prefixunsetNames new branches <prefix>/<workspace>. Unset, the branch is the bare workspace name.
coding_agentclaudeAgent for new workspaces: claude, pi, hermes, codex or omp. See Coding agents.

Agent sessions

KeyDefaultEffect
custom_instructionsunsetText appended to the agent's system prompt on every spawn.
process_doctrinebuilt-in textReplaces the working doctrine injected into every session. off, none or disabled turns it off; an empty value restores the default. See What agents are told.
process_doctrine_extraunsetExtra - ... bullet lines appended to the doctrine.
mcp_mirroronCopy the source repo's MCP servers into each worktree. See MCP server inheritance.

Editor and tools

KeyDefaultEffect
editor_cmd$VISUAL, then $EDITORCommand for e. The worktree path is appended unless the command contains {path}.
terminal_cmd$TERMINALCommand for t, run in the worktree. {cmd} makes it usable for desktop-widget jumps.
diff_cmdunsetCommand for v. Accepts {path} and {base}.
lazygit_cmdunsetCommand for g, run in the worktree. It must open its own window, for example wezterm start -- lazygit.

See Editor, terminal, and diff for examples.

Notifications

KeyDefaultEffect
notificationsonMaster switch for the terminal bell and the ! attention marker.
notification_bell_questiondoubleBell when the agent asks you a question.
notification_bell_completesingleBell when the agent finishes a turn.
notification_bell_permissionsingleBell when the agent waits on a permission prompt.
notification_bell_stalledtripleBell when the agent stalls.

Bell values are off, single, double or triple. off silences the bell but keeps the ! marker. See Status indicators.

Dashboard

KeyDefaultEffect
dashboard_sort_moderecencyWorkspace order within a repo: recency or status. o toggles it.
dashboard_blocked_pin_max_age_secs86400How long a workspace blocked on you stays pinned to the top in recency mode.
top_bar_show_tagsunsetTags, separated by commas or spaces, that keep a workspace in the attached view's top bar when a tag hide rule hides it from the dashboard. A workspace with any listed tag ignores every tag rule; PR hide rules still apply. Names are lowercased, and invalid ones are ignored. After wsx config set, restart wsx to apply; the , drawer applies it at once.
dashboard_branch_width28Width of the branch column, clamped to 10–80.
dashboard_pr_width16Width of the PR column, clamped to 8–24.
detail_bar_configbuilt-inJSON for the detail pane. See Detail bar for the schema.
usage_graph_window24hWindow of the $usage activity graph: 24h, 1w or 1mo. Shown only when a theme places $usage.
pinned_commandsunsetLabel=command lines shown as chips. See Pinned commands.
prompt_tagsunsetname=uses lines, maintained by the prompt-tag picker. See Prompt tags.

Appearance

KeyDefaultEffect
themewsxBase palette: wsx, default (terminal ANSI colors), dracula, jellybeans or nord. Restart wsx to apply.
bar_themeoffRead bar layout and styling from ~/.config/wsx/theme.toml. Applies within a second. See Themes.
nerd_fontsonUse Nerd Font glyphs on the dashboard.

Remote

KeyDefaultEffect
remote_controlonStart claude with --remote-control, so claude.ai/code and the mobile app can reach it. See Remote access.
remote_control_sandboxoffAlso pass --sandbox when remote_control is on.
remotesunsetname=command lines run by wsx remote <name>. See Remote access.
shared_hostsunsetname=ssh-destination lines for browsing and creating workspaces on other machines. See Shared workspaces.

Per-repo settings

Each repo can override or add these values:

SettingCLINotes
Namewsx repo set-name
Branch prefixwsx repo set-prefixOverrides branch_prefix.
Base branchwsx repo set-base-branch
Custom instructionswsx repo set-instructionsAdded to the global custom_instructions.
Setup and archive scriptswsx repo set-setup, set-archiveSee Setup and archive scripts.
Pinned commandswsx repo set-pinned-commandsReplaces the global pinned_commands.
Related reposwsx repo set-related-reposSee Related repos.
Detail bardrawer onlyMerged over the global detail_bar_config.

You can also edit them in the repo settings drawer: press s on any dashboard row.

  • An unset field shows the global value marked (inherited), or (unset).
  • ↑/↓ selects a field. Enter opens the repo value in $EDITOR and saves it when the editor exits.
  • d clears the repo value, which restores inheritance. Esc closes the drawer.

Use a terminal editor that blocks until you quit (vim, helix, nano). A GUI editor that returns immediately saves nothing.

See wsx repo for the full syntax.

Setup and archive scripts

Each repo can have a setup script, which runs when a workspace is created, and an archive script, which runs when a workspace is removed. Use them to install dependencies or clean up build output.

wsx repo set-setup   <repo> 'bun install'
wsx repo set-archive <repo> 'rm -rf node_modules'
wsx repo set-setup   <repo> @./scripts/setup.sh   # from a file
wsx repo edit-setup  <repo>                       # in $EDITOR
wsx repo set-setup   <repo> ""                    # clear

You can also edit both scripts in the repo settings drawer (s on the dashboard). See Per-repo settings and wsx repo.

How scripts run

  • wsx runs each script as $SHELL -ilc "<script>", so your shell profile loads first and tools like mise, direnv and asdf work.
  • If $SHELL is unset or a POSIX-only shell (sh, dash, ash), wsx uses /bin/bash.
  • The working directory is the worktree.
  • Two extra environment variables are set:
VariableValue
WSX_REPO_ROOTThe source repo
WSX_WORKTREEThe workspace's worktree

Setup failures and logs

A failed setup doesn't block the workspace. Its dashboard row shows a ⚙! badge, and a cancelled setup shows ⚙?.

Setup output goes to ~/.local/state/wsx/logs/setup-<repo>-<workspace>.log. Each run overwrites it, and a rename moves it. wsx doesn't keep archive script output.

To read the log in the TUI, or to cancel a setup that's still running, open the workspace card with ? on the dashboard. o opens the log and x cancels the setup. See Keybindings.

The archive script runs before wsx removes the worktree, and you can't cancel it.

Themes

wsx has two layers of theming. The theme setting picks a base palette for the whole UI. The optional bar theme file, theme.toml, controls what the dashboard and attached-view bars show and how they look, using a subset of Starship's format grammar.

Quick start

wsx theme init                  # write the bundled default to ~/.config/wsx/theme.toml
$EDITOR "$(wsx theme path)"
wsx theme check                 # print every error; exit 1 on any
wsx config set bar_theme on
  • bar_theme is off by default. While it's off, wsx draws its stock bars and never reads the file. The wsx theme commands still work, so you can prepare a file first.
  • Switching bar_theme either way, and saving the file, take effect within a second without a restart.
  • If a save has errors, the bars keep their last good look and the dashboard footer shows the first error for five seconds. An invalid file at startup falls back to the bundled default.
  • The file is optional, and anything you leave out keeps the bundled default.
  • The path is $XDG_CONFIG_HOME/wsx/theme.toml when XDG_CONFIG_HOME is an absolute path, and ~/.config/wsx/theme.toml otherwise.
  • wsx theme check <path> validates another file before you install it.

See wsx theme for the command syntax.

Base palette

wsx config set theme nord       # wsx (default), default, dracula, jellybeans, nord

The base palette colors repo headers, the selected row, status dots, modals and markdown. default uses your terminal's ANSI colors. Restart wsx after changing it. The bar theme file can use the palette's colors as theme tokens, but can't change them.

Example themes

Ready-made files live in the repo under docs/examples/:

FileLookPair with theme
theme-starship.tomlPowerline blocks in stepped greys with orange accents.wsx
theme-rose-pine.tomlRosé Pine in an airline layout.wsx
theme-rose-pine-moon.tomlRosé Pine Moon, same layout.wsx
theme-nord.tomlNord, same layout.nord
theme-nord0.tomlNord one step darker, blending into a Nord terminal.nord
theme-jellybeans.tomlJellybeans, same layout.jellybeans
theme-orange.tomlDark orange, from a vim-airline theme.jellybeans
cp docs/examples/theme-starship.toml ~/.config/wsx/theme.toml
wsx config set bar_theme on

They need a Nerd Font and a truecolor terminal. The powerline caps (U+E0B0, U+E0B2) are private-use characters, so copy the files as bytes (cp, scp, git). Pasting through a chat or editor can strip them: wsx theme check still passes, but the blocks lose their edges.

The examples place $statuses, which wsx builds before the segment existed don't recognize: wsx theme check fails, and the running app keeps its last good theme. Use the examples from the release you run.

To switch between several themes, make theme.toml a symlink and re-point it:

mkdir -p ~/.config/wsx/themes
cp docs/examples/theme-*.toml ~/.config/wsx/themes/
ln -sfn themes/theme-nord.toml ~/.config/wsx/theme.toml

The running app reloads within a second. If a switch doesn't show, touch the new target.

Bars

The bundled default:

[dashboard_header]
format       = "$brand(  $filter)    ($statuses)"
right_format = "$counts"
fill         = " "

[dashboard_footer]
format       = "$keys"
right_format = "$funnel"
fill         = " "

[attached_top]
format = "($agent_bar )$workspace(  $workspace_tags)(   $attention)"

[attached_bottom]
format       = "$keys  ($pins  )($tags  )"
right_format = "( ($agents   )($model_tokens )($procs )($diff )$pr)"
fill         = "─"
fill_style   = "fg:dim"

[dashboard_detail]
format     = "($pins  )"
fill       = "─"
fill_style = "fg:dim"

[dashboard_detail_header]
format = "$agent_bar $workspace  $branch(  $pr)(  $diff)(  $procs)  $status"
fill   = " "

[dashboard_detail_reply]
format       = "($pins  )$prompt "
right_format = "$keys"
fill         = " "
BarWhere
dashboard_headerThe dashboard's top line: wordmark, filter echo, status tally, counts. Only the tally's items are clickable. When its formats don't place $statuses, or [statuses] sets disabled, the tally gets its own row below it instead. That depends only on placement: a priority that lets $statuses drop on a narrow terminal doesn't bring the row back.
dashboard_footerThe dashboard's bottom line: key hints and the funnel module.
attached_topThe attached view's top bar.
attached_bottomThe attached view's bottom bar (chip row).
dashboard_detailThe detail pane's pinned-command row. Drawn only when the reply row leaves out $pins or this bar places a module.
dashboard_detail_headerThe detail pane's top row.
dashboard_detail_replyThe detail pane's reply row, laid out like a shell prompt.

Each bar takes these keys:

KeyMeaning
formatThe left side, clipped at the right edge if too long.
right_formatFlush right.
styleBase style for literal text, segment content and the fill.
fillCharacter repeated across the unused gap.
fill_styleStyle for the fill, merged over style.

When both sides are non-empty, at least one blank column separates them.

Overflow

When a bar is too narrow, segments with a priority below 100 drop, lowest first, from either side, until the bar fits. Segments at the default priority of 100 never drop. If it still doesn't fit, wsx omits the right side and clips the left.

The bundled priorities:

BarPriorities
attached_bottommodel_tokens 10, agents 20, procs 30, diff 40, tags 40, pr 50
dashboard_footerhosts 45, version 50, tokens 55, funnel 60, usage 60
dashboard_headersort 30 (when placed), counts 50

The reply row

[dashboard_detail_reply]'s format is the prompt before your draft and right_format is a right prompt after it. The draft fills the gap and always keeps at least 12 cells. To make room, the right side drops first, then the prompt's droppable segments.

$keys holds the ↵ send / Esc cancel hint and shows only while the row has focus (Tab from a selected workspace).

When $pins is on the reply row, on either side, the separate pinned-command row folds away and the chips stay clickable on the reply row. To get the separate row back, leave $pins out: format = "$prompt ". The example themes place the chips on a block:

[dashboard_detail_reply]
format       = "([ $pins ](bg:soot fg:orange)[\ue0b0](fg:soot) )$prompt "
right_format = "([\ue0b2](fg:ash)[ $keys ](bg:ash fg:orange))"

Unplaced segments

The bundled default registers some segments without placing them:

  • $version, the running wsx version: right_format = "$version( $funnel)".
  • $hosts, one clickable chip per shared host: right_format = "$hosts( $funnel)". A chip opens that host's workspace list, like H.
  • $usage, the activity sparkline; usage_graph_window sets its range.
  • $tokens, a module showing context size per agent kind.

A theme that sets its own [attached_bottom].format must add $tags itself to show the prompt-tag chips.

Differences from the stock bars

With bar_theme on:

  • The attached view drops the dim ─ rule under its top bar, so the pane gains a row.
  • A workspace with no PR leaves one blank cell at the chip row's right edge.
  • A narrow dashboard footer drops the version, then the funnel, instead of overflowing.
  • The dashboard header's filter echo is capped at 24 characters and never drops. A long filter drops the counts first.

Grammar

SyntaxMeaning
$name, ${name}Insert a segment (inside a segment's format, one of its variables).
[text](style)Style a run. Inner runs inherit the outer background unless they set their own, so powerline blocks compose.
( … )Render only if a $name inside produced output. Put separators inside the group so they vanish with the segment.
$$, \[, \(, \xLiteral $, [, (, or x.

In TOML double-quoted strings, write a backslash as \\. Single-quoted literal strings take format escapes as-is: '\[$workspace\]'. Use double quotes when you need \ue0b0 to decode to a glyph.

Bare parentheses are a conditional group, so escape a literal pair: \($age\). An unescaped ($age) drops the parentheses.

Styles and colors

A style is space-separated tokens:

  • fg:<color>, bg:<color>, or a bare <color> (foreground).
  • bold, dimmed, italic, underline.
  • none, accepted for Starship compatibility. It does nothing and doesn't reset anything.
  • $style, the segment's resolved style.

A color is one of:

  • #rrggbb, or a 0–255 index.
  • An ANSI name: red, bright-blue, white.
  • A [palette] name.
  • A theme token: dim row_dim path code bg_alt bg_soft ok warn err attention merged header_fg selected_fg selected_bg question stalled waiting thinking complete idle brand wordmark agent_claude agent_pi agent_hermes agent_codex agent_omp. The agent_* tokens are each agent's fixed identity color.

Palette names shadow theme tokens, which shadow ANSI names. This only affects the bar theme. fg:dim is a color, while dimmed is a text modifier.

[palette]
rust = "#d75f00"

Segments

Each segment has its own table, such as [workspace], with these keys:

KeyMeaning
formatThe segment's layout, using its variables.
styleMerged over the segment's state-derived style and exposed as $style.
symbolSubstituted for $symbol.
disabledHide the segment.
priorityOverflow survival; higher lasts longer. Default 100 never drops.
paletteA sub-table of colors for this segment only. See Recoloring one segment.
separatorMulti-item segments: a format drawn between items. It takes no variables.
stylesMulti-item segments: per-position styles. See Grading items by position.
more_formatattention: the tail when entries don't fit. tags: the manager chip. Its one variable is $count.
more_styleattention only: the tail's $style.
symbolsagent_bar only: one glyph per agent kind.
SegmentVariablesNotes
brand$symbol $name $mark $viewThe wordmark. $view is dashboard, not placed by default. Dashboard header only.
group$label $tabsThe group: mode tabs; the active one is highlighted. Not placed by default. Dashboard header only.
sort$label $tabsThe sort: mode tabs. Not placed by default. Dashboard header only.
statuses$glyph $count $labelOne item per status with a nonzero count, most urgent first, counting filtered-out workspaces too. $style is the status color, reversed on the status the list is filtered to and dimmed on the rest. Dashboard header only. Clickable. Older wsx builds reject a theme that uses it.
filter$needleThe live filter, capped at 24 characters. Dashboard header only.
counts$repos $workspacesRepo and workspace counts. Dashboard header only.
keys$key $labelOne pill per key hint. Clickable.
version$version
usage$label $sparkThe activity sparkline. Clickable.
agent_bar$symbolThe agent's glyph in its identity color.
workspace$repo $name$style carries the PR-lifecycle tint (green open, purple merged, red closed). $repo is absent in the detail pane.
workspace_tags$tagOne item per workspace tag, dimmed. Attached view only; empty over a remote attach. A custom attached_top must place it to show tags.
branch$symbol $branchThe git branch. symbol defaults to ⎇.
prompt$symbol $agentThe reply row's prompt character. symbol defaults to ❯. Bold in the agent's color while the row has focus.
status$glyph $label $agoThe workspace's status as its row shows it. $ago is how long it has held. Detail header only.
attention$glyph $repo $name $ageOne item per workspace needing attention. Overflow folds into more_format. Clickable.
pins$index $labelOne chip per pinned command. Clickable.
hosts$index $name $destOne chip per shared host; $name is truncated to 14 columns. Dashboard footer only. Clickable.
tags$index $labelThe three most-used prompt tags, then the manager chip. Attached view only. Clickable.
agents$symbol $icon $label $keyOne pill per agent when there are two or more. $symbol shows which agent is focused, or a spinner while it works. Clickable.
model_tokens$model $tokens$style turns warn near the context limit.
procs$symbol $countHidden at zero. Clickable.
diff$added $removedHidden when clean.
pr$symbol $number $label $mark$style carries the lifecycle tint; $mark_style styles the review verdict. Clickable, except over a remote attach.

Where segments produce output:

BarSegments
Attached barsAll except the dashboard-header segments, branch, prompt, status and hosts. Either bar, either side.
dashboard_footerkeys, version, usage, hosts
dashboard_headerbrand, group, sort, statuses, filter, counts
dashboard_detailpins
dashboard_detail_headeragent_bar, workspace, branch, pr, diff, procs, status
dashboard_detail_replyprompt, agent_bar, workspace, branch, pins, keys

Other segments render empty there, and any segment renders empty when its data is absent. Modules work in every bar.

pr, procs, usage, attention and tags may appear only once per scope. The scopes are: the two attached bars together, the dashboard footer, the dashboard header, and each of the three detail-pane rows. wsx theme check rejects a duplicate.

Gotchas when overriding a segment:

  • style reaches the output only through $style. The stock formats of keys, pins, version, usage and diff don't use $style, so style alone does nothing on them.
  • Emptying symbol leaves the space after $symbol in the stock format. Remove it from format too.
  • An item whose format renders empty is dropped, with no separator or click target.
  • [attention] format = "$items" from older themes is rejected. Delete the table or use the item variables.
  • A built-in segment name, such as hosts, can't be a module name.

Overriding a segment's style

style merges over the segment's state-derived style attribute by attribute. style = "bg:second" keeps the PR's lifecycle foreground, while style = "fg:rust" replaces it.

[workspace]
format = "[($repo/)$name]($style)"
style  = "fg:rust bold"

An explicit style on an inner run still overrides $style. For the PR review mark, use [$mark]($mark_style) to keep its verdict color.

Agent glyphs

[agent_bar.symbols] sets a glyph per agent kind, tried before symbol:

[agent_bar]
symbol = "\ue0b0"        # kinds without an entry

[agent_bar.symbols]
claude = "\uec82"
codex  = "\uec81"

Keys must be claude, pi, hermes, codex or omp. Entries merge over the bundled default. An empty entry (pi = "") shows no glyph. The same glyphs appear as $icon in agents pills and as $icon_<kind> in modules.

Grading items by position

A multi-item segment's styles is a list of styles. The first rendered item's $style is patched by styles[0], the second by styles[1], and items past the end take the last entry. A background-only grade keeps the item's state color in the foreground.

For powerline caps between graded blocks, a multi-item segment's formats can use six extra colors:

ColorMeaning
item_fg, item_bgThis item's final $style colors.
prev_fg, prev_bgThe previous rendered item's colors.
next_fg, next_bgThe next rendered item's colors.

separator sees prev_* and next_*, and more_format sees prev_*. A missing color, such as the last item's next_bg, sets nothing, so the bar's style shows through. That lets the last block's wedge blend into the bar.

attention's more_style gives the fold tail its own colors, so the last entry's next_* points into the tail.

[attention]
styles      = ["bg:charcoal fg:orange", "bg:slate fg:cream", "bg:grey fg:cream"]
more_style  = "bg:orange fg:black"
format      = "[ $glyph $repo/$name \\($age\\) ]($style)[\ue0b0](fg:item_bg bg:next_bg)"
separator   = ""
more_format = "[ +$count more ]($style)[\ue0b0](fg:item_bg)"

Rules wsx theme check enforces:

  • styles and more_style can't use the six neighbor colors.
  • styles and the six colors are rejected on single-item segments.
  • The six names can't be [palette] entries.

Recoloring one segment

A [<segment>.palette] table overrides color names inside that segment only, including the tokens behind its state-derived $style. Use it to darken lifecycle tints on a light block:

[pr.palette]
ok     = "#008700"   # open
merged = "#870087"
err    = "#870000"   # closed
warn   = "#878700"   # conflict

[workspace.palette]
ok        = "#008700"
header_fg = "black"  # no PR
  • Values may name global [palette] entries, but not each other.
  • A name defined only in a segment palette is unknown elsewhere.
  • It changes colors only, not attributes or which token a state uses.

The tokens each state uses:

SegmentTokens
pr, workspaceok open, merged, err closed, warn conflict
workspaceheader_fg without a PR or on a draft
prdim on a draft
attention $glyph, statusesThe six status tokens
group, sortselected_fg, selected_bg, path

Modules

A module is a segment you compose from fleet-wide variables. Declare it as [module.<name>] and place it as $<name>:

[module.funnel]
format   = "([$working working](fg:ok)  )([$blocked blocked](fg:err)  )([$mergeable ready](fg:merged))"
priority = 60

[dashboard_footer]
right_format = "$funnel"
  • A module takes format, style, priority and disabled. It has no click target.
  • A count renders empty at zero, so wrap each item in ( … ) to drop it with its label. $workspaces and $repos always render.
  • tokens_* values render abbreviated (77k, 1.2M) and are empty at zero.
  • The bundled default defines funnel (placed) and tokens (not placed). Set only the fields you want to change.
[dashboard_footer]
right_format = "$tokens(  $funnel)(  $usage)"
VariableCounts
working waiting blocked doneworkspaces whose last wsx status set is that state
busyworkspaces parked on background work
unreportedworkspaces with no reported status
alertsworkspaces with an unacknowledged attention alert
awaiting stalled active idleworkspaces by live transcript classification
live_agentsworkspaces with a live primary session
pr_none pr_draft pr_open pr_conflicted pr_merged pr_closedworkspaces by PR lifecycle
review_required changes_requested approvedPRs by review verdict
unresolvedunresolved review threads
mergeablePRs that are open and approved
dirtyworkspaces with modified or untracked files
msgs_queuedagent-to-agent messages not yet delivered
workspaces repostotals (always rendered)
tokens_totalsum of the latest context size across every agent, primary and peers
tokens_claude tokens_pi tokens_hermes tokens_codex tokens_ompthe same, per agent kind (hermes reports none)
icon_claude icon_pi icon_hermes icon_codex icon_ompthe kind's glyph from [agent_bar.symbols]; like literal text, it never keeps a ( … ) group alive alone

A powerline example

The rounded caps \ue0b6 and \ue0b4 need a Nerd Font. Conditional groups hide whole blocks, caps included, when the segment inside is empty.

[palette]
first  = "#121212"
second = "#3a3a3a"
rust   = "#d75f00"

[attached_top]
format = "[\ue0b6](fg:first)[ $workspace ](bg:first)[\ue0b4](fg:first)( [\ue0b6](fg:second)[ $attention ](bg:second)[\ue0b4](fg:second))"

[attached_bottom]
right_format = "([\ue0b6](fg:second)[ $agents ](bg:second)[\ue0b4](fg:second))(  [\ue0b6](fg:first)[ $pr ](bg:first)[\ue0b4](fg:first))"

[workspace]
format = "[($repo/)$name]($style)"
style  = "fg:rust bold"

[pr]
style = "fg:rust"

Auto-rename

After your first prompt in a new workspace, wsx renames the workspace and its git branch to match the task. Set the WSX_RENAME_MODE environment variable before starting wsx to choose how.

ModeBehavior
claude (default)The coding agent picks a slug from your first message and renames the branch. Costs a few tokens per new session.
localwsx slugifies the text of your first prompt. Costs no tokens.
offNo auto-rename.
WSX_RENAME_MODE=local wsx

Auto-rename only applies to a workspace that still has its generated <adjective>-<plant> name. To rename a workspace by hand, press r on its card (? on the dashboard).

Integrations

wsx hands work off to the tools around it: your editor, terminal and difftool, agents running inside your editor, other repos, other machines, and your desktop bar.

Editor, terminal, and diff

Open the selected workspace in your own tools without leaving wsx. Each key works on a dashboard row and, after Ctrl-x, in the attached view. The tool starts detached, so wsx keeps running.

KeyOpensSettingFallback
eYour editor on the worktreeeditor_cmd$VISUAL, then $EDITOR
tA terminal in the worktreeterminal_cmd$TERMINAL
vYour difftool on the branchdiff_cmdnone
glazygit or another git UI in the worktreelazygit_cmdnone

If no command resolves, wsx shows an error that names the setting to set. See Settings for each key.

Placeholders

PlaceholderReplaced withUsed by
{path}The worktree pathAll four
{base}The upstream base ref, such as origin/maindiff_cmd
{cmd}The wsx command a desktop jump runsterminal_cmd, from desktop widgets

If editor_cmd or diff_cmd contains no placeholder, wsx appends the worktree path as the last argument. terminal_cmd and lazygit_cmd start with the worktree as their working directory instead.

{base} comes from origin/HEAD and falls back to main. If it's wrong, run git remote set-head origin --auto in the repo.

Examples

TUI editors have no terminal of their own, so wrap them in one:

wsx config set editor_cmd "xdg-terminal-exec --dir={path} nvim"

GUI editors work directly:

wsx config set editor_cmd "code"

A diff in a terminal pager. Use three dots so the diff starts at the merge base and a stale local main doesn't add noise:

wsx config set diff_cmd "alacritty -e sh -c 'cd {path} && git diff {base}...HEAD | delta'"

lazygit needs a terminal window, because wsx owns its own:

wsx config set lazygit_cmd "wezterm start -- lazygit"

If your editor hosts its own AI agent, see Editor-hosted agents to give it the workspace's context.

Editor-hosted agents

An AI agent inside your editor (magenta.nvim, Cursor, a VS Code extension) doesn't know what wsx knows about the workspace. wsx context writes that knowledge to one markdown file your editor can hand to its agent, and the agent reports back to the workspace's primary agent with wsx agent send.

The contract

  1. Write the digest. Run wsx context write from the worktree. It prints the file's path and exits 0. Outside a wsx worktree it exits non-zero, so an integration can be installed globally and stay inert elsewhere.

    wsx context show     # print the digest
    wsx context write    # write it to a file and print the path
    
  2. Refresh it. Your editor decides when to rerun wsx context write. Good triggers are editor start, window focus, and opening the agent's chat panel. Guard against overlapping runs on rapid focus events.

  3. Hand it to the agent. Add the path through the editor's context mechanism. If the agent snapshots files when they're added, re-add the file after each refresh.

  4. Report back. The digest tells the agent to report with wsx agent send <primary> "<summary>", run from the worktree. The message arrives at the primary agent without a sender label.

The file lives at $XDG_STATE_HOME/wsx/context/<repo>/<workspace>.md and is readable only by you. It can quote the primary agent's last message, so don't commit or share it. The path follows the workspace name, so it changes after a rename.

See wsx context and wsx agent send.

What the digest contains

  • Repo, workspace, branch, base ref and worktree path.
  • Attached agents, with the primary marked.
  • The last status the primary agent pushed.
  • The recap: goal, state and next step.
  • Up to 20 commits ahead of the base, and a count of uncommitted changes.
  • The primary agent's last message, up to 2,000 characters.
  • External instructions for the editor-hosted agent: check git status and git diff before editing, keep edits small, leave branches, status and recap to the primary agent, and report back with wsx agent send.

Sections with no data are left out. wsx-spawned agents are told that an editor-hosted agent may share their worktree and that its messages arrive unlabeled.

neovim with magenta.nvim

magenta.nvim re-reads its context files before each request, so a digest that wsx keeps fresh stays live. Add this to your neovim config:

local worktrees = vim.fn.expand("~/.local/state/wsx/worktrees/")
local added_path, in_flight = nil, false

local function magenta_visible()
  for _, win in ipairs(vim.api.nvim_list_wins()) do
    local name = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(win))
    if name:find("Magenta Input", 1, true) then return true end
  end
  return false
end

local function wsx_context()
  if in_flight or not vim.startswith(vim.fn.getcwd(), worktrees) then return end
  in_flight = true
  vim.system({ "wsx", "context", "write" }, { text = true }, function(out)
    in_flight = false
    if out.code ~= 0 then return end
    local path = vim.trim(out.stdout)
    if path == "" or path == added_path then return end
    vim.schedule(function()
      if magenta_visible() then
        vim.cmd("Magenta context-files " .. vim.fn.fnameescape(path))
        added_path = path
      end
    end)
  end)
end

vim.api.nvim_create_autocmd({ "VimEnter", "FocusGained" }, { callback = wsx_context })
vim.api.nvim_create_autocmd("BufWinEnter", {
  callback = function(ev)
    if vim.api.nvim_buf_get_name(ev.buf):find("Magenta Input", 1, true) then
      added_path = nil
      wsx_context()
    end
  end,
})
vim.api.nvim_create_user_command("WsxContext", function()
  added_path = nil
  wsx_context()
end, {})
  • The digest refreshes on start and on focus, and attaches when you open the magenta sidebar or start a new thread.
  • The path is added only while the sidebar is open, because :Magenta context-files opens it otherwise.
  • :WsxContext forces a refresh and re-add.
  • If you set $XDG_STATE_HOME, change worktrees to match.

Other editors

Any agent that can read a file can use the digest. Run wsx context write on the editor's start and focus events, ignore a non-zero exit, and pass the printed path to the agent: through a context-file API, or a rules file that tells the agent to read that path. The External instructions block handles the rest.

Related repos

Declare related repos when your work spans several repos that need to know about each other, such as a backend, a frontend and a marketing site. A workspace's agent can then read the related repos' source.

wsx repo set-related-repos backend frontend,marketing
wsx repo edit-related-repos backend      # edit the list in $EDITOR

The value is a comma-separated list of registered repo names. When a workspace in backend starts its agent, wsx:

  • passes each related repo's source path with --add-dir (Claude Code and oh-my-pi);
  • tells the agent to treat those paths as read-only;
  • tells the agent that when a related repo needs changes, it should create a workspace there with wsx workspace create, hand the task to that workspace's agent with wsx agent send, and tell you which workspace owns it.

Each repo gets its own branch and pull request. Merge them in dependency order.

The read-only rule is an instruction, not a lock. Installing the wsx skill (see Agent skills) reinforces it. Unknown repo names in the list are skipped.

See wsx repo set-related-repos.

MCP server inheritance

Claude Code keys its MCP server config on the directory it starts in, so an agent in a worktree wouldn't see the MCP servers you added in the source repo. wsx copies the source repo's mcpServers from ~/.claude.json into the worktree's entry each time a workspace's agent starts.

  • Servers you add to the source repo with claude mcp add reach a workspace the next time its agent starts.
  • Archiving a workspace removes its entry from ~/.claude.json.
  • MCP configs often hold API tokens. Copying puts the same secret under a second key in the same file.

To turn it off, set mcp_mirror to false (see Settings). wsx then never reads or writes ~/.claude.json, and you can still add servers per workspace with claude mcp add while attached.

wsx config set mcp_mirror false

Remote access

Reach wsx from another machine or your phone. Run the whole dashboard in tmux and attach over ssh, drive a single Claude Code session from claude.ai, or attach to one agent with shared workspaces.

You wantUse
The full dashboard from another computertmux and ssh
One reconnect command per machineNamed remotes
A Claude Code session from your phone or browserRemote control
One agent from another machine, or from the dashboard's host pickerShared workspaces

tmux and ssh

On the host, start wsx inside tmux, or reattach if it's already running:

tmux new -As wsx 'wsx'

From any other machine:

ssh desktop -t tmux attach -t wsx

Workspaces and their agents keep running while you're detached.

  • wsx's leader key is Ctrl-x, so it doesn't collide with tmux's Ctrl-b prefix.
  • On a flaky network, use mosh: mosh desktop -- tmux attach -t wsx.
  • Tailscale or another VPN makes the host reachable by name without port forwarding.

Named remotes

Save a reconnect command under a short name, then run it with wsx remote:

wsx remote                 # list saved names
wsx remote desktop         # run the saved command
wsx config edit remotes    # edit the list in $EDITOR

The remotes setting holds one name=command per line:

desktop=ssh -t desktop 'tmux attach -t wsx'
gpu=ssh gpu-box -t 'tmux -u attach -t main || tmux -u new -s main'
  • There is no wsx remote add. Add a line in the editor, and keep the existing lines unless you mean to drop them.
  • Only the first = splits name from command, so the command can contain =.
  • The command runs with sh -c and replaces the wsx process. When it exits, you're back at your shell.
  • A duplicate name uses its last value.

See wsx remote.

Remote control

wsx starts Claude Code with --remote-control, so each Claude session prints a URL and a QR code you can open on claude.ai/code or the Claude mobile apps. It applies to Claude Code agents only.

SettingEffect
remote_controlSet to false to keep sessions local-only.
remote_control_sandboxSet to true to run remote-issued commands in Claude Code's --sandbox.
  • The relay uses your claude.ai account. Signed out or offline, the local session still works.
  • Session state goes through Anthropic's relay, and anyone who can see your screen can see the session URL.

See Settings.

Shared workspaces

A shared workspace runs its agents inside tmux instead of as children of wsx. Quitting wsx or dropping your ssh connection doesn't stop them, and you can attach to an agent from another terminal or another machine.

Requires tmux 3.2 or later.

Create or convert

wsx workspace create <repo> --shared
wsx workspace share <repo> <slug>
wsx workspace unshare <repo> <slug>

On the dashboard:

KeyAction
SNew shared workspace. In the new-workspace form, Ctrl-s toggles shared.
TToggle the selected workspace between shared and direct, after a confirmation.

A running agent can't move into or out of tmux, so it must restart:

  • T restarts running agents right away and resumes their conversations. Stopped agents start in tmux.
  • share and unshare change only the flag. Running agents keep their old mode until they restart.
  • share --restart asks the dashboard on that host to do what T does, then waits and prints the tmux session names. It needs wsx running on the host, and fails without changes if no dashboard answers within a few seconds.

See wsx workspace share.

On the dashboard

A shared workspace shows a badge left of its branch: a network icon with nerd fonts, ◇ without.

Badge colorMeaning
GreenThe tmux session is alive, even if this wsx isn't attached to it.
RedNo live session backs it, so nobody can attach.

After you restart wsx, a shared workspace's agent is still running. Press Enter on the row to reattach with its history intact.

Attach from another terminal

Each agent runs in a tmux session you can attach to directly:

tmux attach -t wsx-<repo>-<workspace>

Extra agents get wsx-<repo>-<workspace>-<agent><n>, for example wsx-myrepo-fix-bug-codex2. Characters other than letters, digits, _ and - become -. The name is fixed when the session starts, so renaming the workspace doesn't change it.

List shared workspaces and their sessions:

wsx shared list            # repo, workspace, session, alive/(dead)
wsx shared list --all      # also every ready workspace that isn't shared
wsx shared list --json

See wsx shared list.

Browse another machine

List the hosts in the shared_hosts setting, one name=ssh-destination per line:

wsx config edit shared_hosts
mini=eben@ebenmini.local
lab=user@lab.example.com

Press H on the dashboard to pick a host. For one-click access, add the $hosts segment to your dashboard footer (see Themes).

wsx lists the host's workspaces: live agents first (agent, branch with PR number and tags, repo/workspace), then dimmed rows marked not shared or stopped.

KeyAction
j / kMove.
EnterAttach to a live agent. On a not shared or stopped row, share and restart it on the host after a confirmation, then attach.
n / NCreate a workspace on the host, normal or yolo.
rRefresh the list.
EscClose.
  • Sharing from the list restarts the agent, which cuts off what it was doing. Its conversation resumes.
  • Ctrl-x d detaches from a remote agent and returns to the dashboard. The agent keeps running on the host.

Create a workspace on the host

n opens a form preset to the selected row's repo:

KeyAction
↑ / ↓Change repo.
TypeSet the name. Leave it empty for a generated name.
TabCycle the agent, starting from the host's default.
Ctrl-yToggle yolo.
EnterCreate it on the host as shared, run its setup script, start its agents, and attach.
  • If the workspace can't be created (name taken, unknown repo), the form returns with the host's reason.
  • If it's created but its agents don't start, it appears in the list with the reason. Press Enter on it once the host is fixed.
  • If its setup script fails, wsx doesn't attach. The list shows the exit code and the setup log path on the host.
  • Creating gives up after 15 minutes. The host may still finish, so press r before you retry.

Names that would need shell quoting can't be shared or created from the list. A host running an older wsx shows only its live shared sessions, with no sharing or creating.

Requirements for remote hosts

  • Key-based ssh access. The background list fetch can't answer a password prompt.
  • wsx and tmux on the PATH of a login sh on the host. Check with ssh <host> "sh -lc 'which wsx tmux'". On macOS, PATH set only in ~/.zshrc or ~/.zprofile (including Homebrew's) is invisible to sh -l, so add it to ~/.profile too.
  • The wsx dashboard running on the host to share or create from the list. Attaching to an already-shared workspace doesn't need it.
  • Locally, only ssh.

Scrollback

Reattaching shows only the session's current screen. wsx's scrollback starts fresh with each attach. tmux keeps its own scrollback, which you can reach with tmux copy mode (Ctrl-b [).

Desktop widgets

A desktop widget shows your live workspace count in the system bar, colored by the most urgent status, and jumps to any workspace in one click. wsx ships one for waybar, one for KDE Plasma 6, and one for the macOS menubar.

Every widget:

  • shows a branch icon and the count of workspaces across every repo;
  • takes its color from the most urgent status: blocked, done, waiting, or working;
  • shows a per-workspace tooltip on hover;
  • jumps to a workspace by opening it in the running TUI, attached, or by launching a new TUI in a terminal.

The setup command bakes in the path to wsx. Re-run it after you move or reinstall wsx somewhere else.

Waybar (Linux)

wsx setup waybar

Setup writes wsx.jsonc and wsx.css into ~/.config/waybar/ and adds the custom/wsx module to config.jsonc, saving a timestamped backup first. If it doesn't recognize your config's layout, it prints the lines to paste instead.

Click the module to open a workspace picker. It uses walker by default. Set WSX_WAYBAR_MENU to use another menu command.

When walker and elephant are installed, setup also adds an elephant menu and a wsx walker theme, so the picker shows PR numbers, review marks, and diff counts:

  • The theme takes its colors from your current walker theme. Re-run wsx setup waybar after you switch walker themes.
  • Setup restarts elephant for you. Restart walker yourself to load the theme.

To remove it, delete "custom/wsx" and the wsx include entry from config.jsonc, or restore the backup.

See wsx setup waybar and wsx waybar.

KDE Plasma applet (Linux)

Requires KDE Plasma 6.

wsx setup plasma

Setup installs the applet into ~/.local/share/plasma/plasmoids/io.github.bakedbean.wsx. To add it to a panel, right-click the panel, choose Add or Manage Widgets, and search for "wsx".

Click the applet to list every repo's workspaces with their status glyph, status message, and PR number. Pick one to jump to it and raise its window. If the jump fails, the popup shows the error.

  • After you re-run setup with the applet already on a panel, restart plasmashell to load the new version:

    kquitapp6 plasmashell && kstart plasmashell
    
  • A terminal that serves several windows from one process can raise the wrong window of that terminal.

To remove it:

kpackagetool6 --type Plasma/Applet --remove io.github.bakedbean.wsx

See wsx setup plasma and wsx desktop.

macOS menubar (SwiftBar)

Requires SwiftBar. Launch SwiftBar once and pick a plugin folder before you run setup.

brew install swiftbar
wsx setup menubar

Setup installs a wsx-menubar.10s.sh plugin into SwiftBar's plugin folder.

The dropdown lists every repo and its workspaces, with status glyph, dirty marker, diff stats, and PR number. Each workspace has a submenu:

ItemAction
JumpOpen the workspace in the TUI.
Open PROpen the pull request in your browser.
Copy worktree pathCopy the path to the clipboard.
Reveal in FinderShow the worktree in Finder.

A Project Manager submenu lists each workspace's recap (goal, state, and next step), blocked and waiting workspaces first. It mirrors the dashboard's project manager digest. Click a line to jump to that workspace.

To remove it, delete wsx-menubar.10s.sh from the plugin folder.

See wsx setup menubar and wsx menubar.

Which terminal a jump opens

With no TUI running, a jump launches one in the first of these that applies:

OrderLinuxmacOS
1terminal_cmd, if it contains {cmd}terminal_cmd, if it contains {cmd}
2$TERMINALiTerm2
3konsole, in a Plasma sessionTerminal
4alacritty

terminal_cmd is also the dashboard's [t] terminal, which doesn't fill in {cmd}. If you use [t], leave {cmd} out of terminal_cmd and let the jump pick a terminal from the rest of the list. See Editor, terminal, and diff.

CLI reference

Every wsx command and flag, grouped by command. Run wsx with no arguments to open the dashboard. wsx --help lists the command groups, wsx <group> --help lists a group's commands, and wsx --version prints the version.

wsx                          # open the dashboard
wsx --select <repo>/<slug>   # open the dashboard with a workspace selected
wsx <group> --help
wsx --version

Conventions

ConventionMeaning
<value-or-@file>A literal value, or @path to read the value from a file.
"" as a valueClears the setting. Per-repo values then fall back to the global setting.
Current workspaceCommands without --workspace act on $WSX_WORKSPACE_ID, else the worktree containing the current directory.
--workspace <repo>/<slug>Targets another workspace instead of the current one.
--jsonPrints machine-readable output. Timestamps are epoch milliseconds.

wsx sets $WSX_WORKSPACE_ID and $WSX_AGENT_INSTANCE_ID in every agent session it spawns, so commands run by an agent know their workspace and sender. See Environment variables.

Command groups

GroupPurposePage
wsx workspaceCreate, list, rename, share, tag and archive workspaceswsx workspace
wsx repoRegister repositories and set per-repo settingswsx repo
wsx agentAdd, remove and message agentswsx agent
wsx statusReport and read agent statuswsx status, recap, context
wsx recapMaintain the workspace recapwsx status, recap, context
wsx contextPrint a context digest for editor-hosted agentswsx status, recap, context
wsx configGet and set global settingsOther commands
wsx themeValidate and scaffold the bar theme fileOther commands
wsx remoteRun saved remote shortcutsOther commands
wsx sharedList tmux-shared workspacesOther commands
wsx setupInstall the agent skills and desktop widgetsOther commands
wsx waybarLinux waybar module and workspace jumperOther commands
wsx desktopDesktop-neutral status and jump for panel widgetsOther commands
wsx menubarmacOS SwiftBar module and workspace jumperOther commands

wsx workspace

Commands that create, inspect and retire workspaces. Each workspace is a git worktree on its own branch, with one or more agents.

wsx workspace create

wsx workspace create <repo> [--name <slug>] [--yolo] [--shared] [--start [--json]] [--agent <kind>] [--prompt <text>]

Creates a branch and worktree in <repo>, like n on the dashboard.

FlagEffect
--name <slug>Workspace name. The branch is <branch_prefix>/<slug>. Default: a generated name like merry-birch.
--yoloSkip permission prompts in the agent session.
--sharedRun the workspace's agents under tmux. See Shared workspaces.
--startHave the running dashboard start the agents in tmux, wait until they're live, and print the sessions to attach to. Implies --shared.
--jsonWith --start only: print the workspace record on stdout, even when the start fails.
--agent <kind>claude, pi, hermes, codex or omp. Overrides the coding_agent setting. See Coding agents.
--prompt <text>Queue a first task for the primary agent, like an immediate wsx agent send.
  • Run from inside a workspace, create inherits that workspace's yolo mode and agent kind unless you pass --yolo or --agent. It prints what it inherited.
  • wsx refuses a slug that git can't use in a branch name, or whose branch already exists or clashes with an existing branch path (wsx/foo blocks wsx/foo/bar).
  • A failing setup script prints a warning and its log path. The workspace is still created.
  • --start and --prompt need a running dashboard. Without one, --start fails after creating the workspace, and a --prompt stays queued until a dashboard starts.

Starting work from a phone over SSH, with a dashboard already running in tmux on the host:

wsx workspace create backend --prompt "Fix the flaky input PTY tests"

Without --name, the agent renames the workspace and branch from the prompt. See Auto-rename.

wsx workspace list

wsx workspace list [<repo>] [--json]

Lists workspaces as tab-separated repo slug branch worktree_path rows, optionally for one repo.

--json prints one object per workspace with everything its dashboard row shows:

{
  "repo": "backend",
  "slug": "add-widgets",
  "branch": "eg/add-widgets",
  "path": "/home/me/.local/state/wsx/worktrees/backend/add-widgets",
  "status": {"state": "working", "message": "running tests", "source": "model", "reported_at": 1790342366562},
  "recap": {"goal_short": "POST /widgets", "state_short": "tests failing", "next_short": "fix validation"},
  "agents": [{"id": 12, "label": "claude", "kind": "claude", "primary": true, "status": {"state": "working", "...": "..."}}],
  "pr": {"state": "open", "number": 241, "url": "https://github.com/acme/backend/pull/241", "review": "approved", "unresolved": 0, "fetched_at": 1790342836000},
  "tags": ["spike"]
}
  • status is the workspace's combined status. See Status and recap.
  • pr is the last PR state the dashboard or a desktop widget cached. list makes no network calls. pr.state is one of no_pr, draft, open, conflicted, merged or closed.
  • A field that was never set is null.

wsx workspace path

wsx workspace path <repo> <slug>

Prints the workspace's worktree path, for cd "$(wsx workspace path backend my-slug)".

wsx workspace rename

wsx workspace rename <repo> <old-slug> <new-slug>

Renames the workspace and its git branch together. Use this instead of git branch -m, which leaves wsx out of sync.

  • The worktree directory doesn't move. It keeps the old slug, and the command prints the unchanged path.

wsx workspace archive

wsx workspace archive <repo> <slug> [--keep-worktree] [--force-delete-branch]

Retires a workspace, like the dashboard's archive action. In order, it:

  1. stops the workspace's tmux agent sessions;
  2. stops tracked processes under the worktree (SIGTERM, then SIGKILL after two seconds), unless --keep-worktree;
  3. runs the repo's archive script;
  4. force-removes the worktree, unless --keep-worktree. Uncommitted changes are lost.
  5. deletes the branch, then unregisters the workspace.
FlagEffect
--keep-worktreeKeep the worktree directory and leave its processes running.
--force-delete-branchDelete the branch even if it isn't merged. Without it, an unmerged branch is kept.

wsx workspace share

wsx workspace share <repo> <slug> [--restart [--json]]

Switches a workspace to tmux-backed agent sessions, so other terminals or machines can attach. See Shared workspaces.

FlagEffect
--restartHave the running dashboard restart the agents in tmux now, and print the sessions to attach to.
--jsonWith --restart only: print the workspace record as JSON.
  • Without --restart, running sessions keep their current backend until they restart.

wsx workspace unshare

wsx workspace unshare <repo> <slug>

Switches a workspace back to direct (non-tmux) agent sessions. Running sessions change on their next restart.

wsx workspace tag

wsx workspace tag add|rm <tag>... [--workspace <repo>/<slug>]
wsx workspace tag list [--workspace <repo>/<slug> | --all] [--json]

Adds, removes or lists workspace tags on the current workspace, then prints its tags.

FlagEffect
--workspace <repo>/<slug>Act on another workspace.
--alllist only: print tag<TAB>count for every tag in use.
--jsonlist only: print an array of tags, or {"tag", "workspaces"} objects with --all.
  • Tags are lowercased. Each must be 1–32 characters of a-z 0-9 - _, starting with a letter or digit. If any tag is invalid, nothing is written.

wsx repo

Commands that register git repositories with wsx and set their per-repo settings. <name> is the repo's wsx name, as shown by wsx repo list. Values marked <value-or-@file> accept @path to read from a file and "" to clear. The edit-* commands open the current value in $EDITOR.

wsx repo add

wsx repo add <path> [--name <name>] [--prefix <prefix>]

Registers the git working tree at <path>.

FlagEffect
--name <name>Name on the dashboard and in commands. Default: the directory name.
--prefix <prefix>Branch prefix for this repo. Usually omit it and set the global branch_prefix instead.

wsx repo list

wsx repo list [--json]

Lists registered repos and their paths. --json prints {name, path, branch_prefix} objects.

wsx repo remove

wsx repo remove <name>

Unregisters a repo and all its workspaces. Nothing is deleted on disk: the repo and its worktrees stay where they are.

wsx repo set-name

wsx repo set-name <name> <new-name>

Renames the repo in wsx. Later commands, such as wsx workspace create, use the new name.

wsx repo set-path

wsx repo set-path <name> <path>

Points the repo at a source checkout that moved, keeping its name, settings and workspaces. Use it instead of remove and add.

  • New workspaces branch from the new path. Existing worktrees came from the old checkout, so recreate them if the old path is gone.

wsx repo set-prefix

wsx repo set-prefix <name> <prefix>

Sets the repo's branch prefix. "" clears it, falling back to the global branch_prefix.

wsx repo set-base-branch

wsx repo set-base-branch <name> <ref>

Sets the ref new workspaces branch from, such as origin/main. wsx fetches it before each create. The dashboard's diff column also compares against it.

  • "" clears it. New workspaces then branch from the source checkout's current HEAD.

wsx repo set-instructions

wsx repo set-instructions <name> <value-or-@file>

Sets instructions added to the agent's system prompt in this repo's workspaces, after the global custom_instructions.

wsx repo set-setup

wsx repo set-setup <name> <value-or-@file>
wsx repo edit-setup <name>

Sets the script that runs in each new worktree. See Setup and archive scripts.

wsx repo set-archive

wsx repo set-archive <name> <value-or-@file>
wsx repo edit-archive <name>

Sets the script that runs before a worktree is archived. See Setup and archive scripts.

wsx repo set-pinned-commands

wsx repo set-pinned-commands <name> <value-or-@file>
wsx repo edit-pinned-commands <name>

Overrides the global pinned_commands for this repo. "" falls back to the global value. See Pinned commands.

wsx repo set-related-repos <name> <value-or-@file>
wsx repo edit-related-repos <name>

Sets a comma-separated list of other registered repos, such as frontend,marketing, that agents in this repo can also read. Unknown names are skipped. See Related repos.

wsx agent

Commands that add agents to a workspace and pass messages between them. See Multi-agent workspaces for how agents share a worktree.

An agent's label is its kind, with #N for the second and later agents of that kind: claude, claude#2, codex. primary always names the workspace's primary agent. An all-digit target is an instance id, which is unique across workspaces. send, reply, messages and wait identify you by $WSX_AGENT_INSTANCE_ID, which wsx sets in each agent session.

wsx agent list

wsx agent list [--workspace <repo>/<slug>] [--json]

Lists the workspace's agents with their instance ids and each agent's last status.

1  claude  (primary)  working — "running the test suite" (model, 3m ago)
2  claude#2  done (hook, 10m ago)
4  codex

--json prints {id, label, kind, primary, status} objects. status is {state, message, source, reported_at} or null.

wsx agent add

wsx agent add <kind>

Adds an agent of <kind> (claude, pi, hermes, codex or omp) to the current workspace and prints its label. The dashboard starts it.

wsx agent remove

wsx agent remove <label>

Removes an agent from the current workspace.

  • You can't remove the primary agent. Archive the workspace instead.
  • Messages still queued for the agent are discarded. The command reports how many.

wsx agent send

wsx agent send [--workspace <repo>/<slug>] [--file <path>|-] <label|instance-id> [<message...>|-]

Queues a message for an agent and prints its id, for example queued message #561 to claude#2 (2747 bytes).

FlagEffect
--workspace <repo>/<slug>Look up the label in another workspace.
--file <path>Read the body from a file. --file - or a body of - reads stdin.
  • The running dashboard delivers the message when the target is idle, starting the agent first if needed. With no dashboard running, send warns and the message waits.
  • The recipient sees a banner with the message id, the sender and the reply command.
  • Flags can go before or after the target. From the first message word on, everything is body text. To send text containing --file or --workspace, quote the whole message.
  • An empty body is refused. So is a target whose agent binary isn't installed.

wsx agent reply

wsx agent reply [--file <path>|-] [<msg-id>] [<message...>|-]

Queues a message to the sender of <msg-id> (561 or #561), wherever it lives. Without an id it answers the latest message you received.

  • The first word counts as an id only when a body follows, so wsx agent reply 42 sends "42". Prefer the explicit id from the banner.
  • Messages sent from a shell or an editor agent have no sender to reply to.

wsx agent messages

wsx agent messages [--sent|--all] [--undelivered] [--limit <n>] [--id <msg-id>] [--json]

Lists your inbox as tab-separated rows, oldest first.

ID   FROM                           TO      BYTES  CREATED               DELIVERED
561  claude#2                       claude  2747   2026-09-25T14:03:11Z  2026-09-25T14:03:14Z
562  workspacex/parent-task claude  claude  812    2026-09-25T14:10:40Z  queued
FlagEffect
--sentList messages you sent.
--allList every message in the current workspace. Works from a plain shell.
--undeliveredOnly queued and dropped messages.
--limit <n>Show the newest n. Default: 20.
--id <msg-id>Print one message's headers and full body.
--jsonPrint {id, from_id, from, to_id, to, workspace, bytes, body, created_at, delivered_at, state, drop_reason} objects.
  • DELIVERED reads dropped when wsx gave up on a message, for example because the agent's binary is missing. --id shows the reason.

wsx agent wait

wsx agent wait [--from <sender>] [--after <msg-id>] [--timeout <secs>] [--done <agent>]

Blocks until a message for you arrives, then prints it like messages --id.

FlagEffect
--from <sender>Only messages from this agent: a label, primary, an instance id, or <repo>/<slug> <label>.
--after <msg-id>Only messages newer than this id. Without it, already-queued messages count.
--timeout <secs>Give up after this long. Default: 110. 0 waits forever.
--done <agent>Also return when this agent reports done or blocked after the wait (or the --after message) began. Works from a plain shell.
  • wait doesn't consume the message. The dashboard still injects it into your session, so you see it twice. Chain waits with --after <last id>.
  • For a request and reply: send a task, then wait --after <its id> --done <peer>.

wsx agent whoami

wsx agent whoami

Prints your agent label, instance id, workspace and whether you're the primary.

wsx status, recap, context

Commands an agent uses to report what it's doing, and that you or another agent use to read it back. See Status and recap for how the dashboard shows them.

wsx status set

wsx status set <working|waiting|blocked|done> [--message <text>] [--goal <text>] [--state <text>] [--next <text>] [--goal-short <text>] [--state-short <text>] [--next-short <text>]

Sets the calling agent's status in the current workspace.

FlagEffect
--message <text>, -m <text>A short note shown with the status.
--goal, --state, --next and their -short formsUpdate the recap in the same call. See wsx recap set.
  • From a shell without $WSX_AGENT_INSTANCE_ID, the status is recorded against the primary agent.
  • done, blocked and waiting hold until the agent starts new work. working holds until the turn ends. See Status and recap for the rules.

wsx status show

wsx status [show] [--workspace <repo>/<slug>] [--json]

Prints the workspace's combined status, then each agent's. Bare wsx status does the same.

status: blocked — "need your call on the schema" (model, 1m ago)
agents:
  claude (primary): working — "implementing" (model, 4m ago)
  claude#2: blocked — "need your call on the schema" (model, 1m ago)

--json prints {repo, slug, status, agents}, with the same shapes as wsx agent list --json.

wsx status clear

wsx status clear

Clears status in the current workspace. Run by an agent, it clears only that agent's status. Run from a shell, it clears every agent's.

wsx status from-hook

wsx status from-hook [--agent <kind>]

Reads an agent hook's JSON event on stdin and updates the status. wsx installs this as the agents' status hook, so you don't run it yourself. It always exits 0 and prints nothing. Codex uses wsx status from-notify the same way.

wsx recap set

wsx recap set [--goal <text>] [--state <text>] [--next <text>] [--goal-short <text>] [--state-short <text>] [--next-short <text>]

Updates the current workspace's recap. Pass at least one flag. Fields you omit keep their value.

FlagField
--goalWhat the workspace is for.
--stateWhere the work stands.
--nextThe next step.
--goal-short, --state-short, --next-shortKeyword versions for the dashboard row: identifiers, ticket and PR numbers, no filler.

wsx recap show

wsx recap [show] [--workspace <repo>/<slug>] [--json]

Prints the recap. Bare wsx recap does the same.

wsx recap clear

wsx recap clear

Deletes the current workspace's recap.

wsx context show

wsx context show [--workspace <repo>/<slug>]

Prints a markdown digest of the workspace for an editor-hosted agent: branch, agents, status, recap, recent commits, the primary agent's last message, and instructions for the external agent. See Editor-hosted agents.

wsx context write

wsx context write

Writes the current workspace's digest to $XDG_STATE_HOME/wsx/context/<repo>/<slug>.md (default ~/.local/state) and prints the path.

  • Outside a wsx worktree it exits non-zero, so a global editor hook stays inert elsewhere.
  • The file can quote the agent's last message. Don't commit or share it.
  • The path follows the workspace name. After a rename, the old file stays behind.

Other commands

Settings, themes, remote shortcuts, shared workspaces, setup helpers and desktop widgets.

wsx config get

wsx config get <key>

Prints a global setting's value, or (unset). See Settings for every key.

wsx config set

wsx config set <key> <value-or-@file>

Sets a global setting. @path reads the value from a file, and "" clears the setting back to its default. An unknown key is an error.

wsx config list

wsx config list

Lists every setting that has a value.

wsx config edit

wsx config edit <key>

Opens a setting's value in $EDITOR and saves it when you exit. Use it for multi-line values such as remotes and pinned_commands.

wsx theme check

wsx theme check [<path>]

Validates the bar theme file and prints every error. Without <path> it checks ~/.config/wsx/theme.toml, and a missing file there passes. See Themes.

  • It also warns when the bar_theme setting is off, since the file then has no effect.

wsx theme path

wsx theme path

Prints where wsx looks for theme.toml.

wsx theme init

wsx theme init

Writes the bundled default theme.toml to that path. It refuses to overwrite an existing file.

wsx remote

wsx remote [<name>]

Without a name, lists your saved remote shortcuts. With a name, replaces the wsx process with sh -c <command>. See Remote access.

  • Shortcuts live in the remotes setting as one name=command line each. Add one with wsx config edit remotes. There is no wsx remote add.

wsx shared list

wsx shared list [--json] [--all]

Lists tmux-shared workspaces as tab-separated repo slug session alive rows, one per agent. See Shared workspaces.

FlagEffect
--allAlso list every ready workspace that isn't shared.
--jsonPrint records with each workspace's PR status. Needs gh for the PR status.

wsx setup install-skill

wsx setup install-skill

Installs or updates the bundled wsx, agent-review and handoff skills for Claude Code, and for Codex and Hermes when they're installed. See Agent skills.

wsx setup waybar

wsx setup waybar

Installs the waybar module into ~/.config/waybar, plus the elephant menu and walker theme when those are installed. Linux only. See Desktop widgets.

wsx setup plasma

wsx setup plasma

Installs the KDE Plasma 6 panel applet. Linux only. See Desktop widgets.

wsx setup menubar

wsx setup menubar

Installs the SwiftBar plugin. macOS only. See Desktop widgets.

wsx waybar status

wsx waybar status

Prints the JSON for the waybar custom/wsx module. Linux only, like every wsx waybar command.

wsx waybar menu

wsx waybar menu

Opens a workspace picker and jumps to the one you choose. It uses walker unless WSX_WAYBAR_MENU names another menu command.

wsx waybar jump

wsx waybar jump <repo> <slug>

Selects the workspace in a running dashboard, or opens a new one in a terminal (terminal_cmd) with it selected.

wsx waybar menu-entries

wsx waybar menu-entries [--json]

Prints the workspace entries for the elephant menu as JSON.

wsx waybar refresh-prs

wsx waybar refresh-prs

Refreshes the cached PR state for every workspace. Needs gh.

wsx desktop status

wsx desktop status

Prints a JSON summary for panel widgets: count, class (the most urgent state), tooltip, and rows of {repo, slug, state, message, pr_number}.

wsx desktop jump

wsx desktop jump <repo> <slug>

Selects the workspace in a running dashboard and raises its window, or opens a new one. Linux only.

wsx menubar plugin

wsx menubar plugin

Prints the SwiftBar plugin document. macOS only, like every wsx menubar command.

wsx menubar jump

wsx menubar jump <repo> <slug>

Selects the workspace in a running dashboard, or opens a new one in a terminal.

wsx menubar copy-path

wsx menubar copy-path <repo> <slug>

Copies the workspace's worktree path to the clipboard.

wsx menubar refresh

wsx menubar refresh

Refreshes the cached git and PR indicators for every workspace.

Reference

Lookup tables for the environment wsx reads and the files it keeps.

Environment variables

Set these in the shell that starts wsx. wsx passes its environment on to the agents it starts.

Agent binaries and models

VariablePurpose
WSX_CLAUDE_BINPath to claude. Default: claude on PATH.
WSX_PI_BINPath to pi. Default: pi on PATH.
WSX_PI_MODELModel pattern passed to pi as --model.
WSX_PI_PROVIDERLimits pi to one provider's models.
WSX_HERMES_BINPath to hermes. Default: hermes on PATH.
WSX_HERMES_MODELModel for Hermes, overriding ~/.hermes/config.yaml.
WSX_HERMES_PROVIDERProvider passed to Hermes as --provider. Hermes's default REPL mode may ignore it in favour of ~/.hermes/config.yaml.
WSX_CODEX_BINPath to codex. Default: codex on PATH.
WSX_CODEX_MODELModel passed to Codex as -m.
WSX_OMP_BINPath to omp (oh-my-pi). Default: omp on PATH.
WSX_OMP_MODELModel passed to omp as --model.

See Coding agents.

Other tools

VariablePurpose
WSX_TMUX_BINPath to tmux, for shared workspaces.
WSX_SSH_BINPath to ssh, for browsing remote hosts.
WSX_WAYBAR_MENUMenu command for the Waybar workspace picker. Default: walker --dmenu.
VISUAL, EDITOREditor for e when editor_cmd is unset.
EDITOREditor for wsx config edit, the edit-* commands, and repo settings. Default: vi.
TERMINALTerminal for t when terminal_cmd is unset.
SHELLShell for setup and archive scripts. Falls back to bash for sh, dash and ash.

Behavior and diagnostics

VariablePurpose
WSX_RENAME_MODEAuto-rename mode: claude (default), local, or off. See Auto-rename.
RUST_LOGLog filter. Default: info. Use wsx=debug for verbose logs.
WSX_INPUT_TRACESet to 1 to log every key and mouse event to wsx.log.

Paths

VariablePurpose
XDG_STATE_HOMEBase of the state directory. Default: ~/.local/state.
XDG_CONFIG_HOMEBase of the config directory. Default: ~/.config.
XDG_RUNTIME_DIRBase of the directory for the dashboard's control sockets.

Set by wsx

wsx sets these for the processes it starts.

VariableSet forPurpose
WSX_WORKSPACE_IDAgent sessionsThe workspace the session belongs to.
WSX_AGENT_INSTANCE_IDAgent sessionsThe agent instance, used to sign and receive messages.
WSX_REPO_ROOTSetup and archive scriptsThe source repo's path.
WSX_WORKTREESetup and archive scriptsThe workspace's worktree path.

See Multi-agent workspaces and Setup and archive scripts.

Storage and configuration files

wsx keeps its state under $XDG_STATE_HOME/wsx/ (default ~/.local/state/wsx/) and its config under $XDG_CONFIG_HOME/wsx/ (default ~/.config/wsx/).

wsx's own files

PathContents
~/.config/wsx/theme.tomlOptional bar theme, reloaded while wsx runs. See Themes.
~/.local/state/wsx/state.dbSQLite database of repos, workspaces, agents and settings.
~/.local/state/wsx/worktrees/<repo>/<workspace>/Workspace worktrees.
~/.local/state/wsx/context/<repo>/<workspace>.mdContext digest from wsx context write. See Editor-hosted agents.
~/.local/state/wsx/logs/wsx.log.<date>The dashboard's log, one file per day.
~/.local/state/wsx/logs/setup-<repo>-<workspace>.logThe last setup script run for a workspace.
~/.local/state/wsx/logs/ws<id>-<time>.logOutput of a command run in the background.
~/.local/state/wsx/omp-config.ymlConfig overlay passed to oh-my-pi.
~/.local/state/wsx/pi-session-report.tsExtension passed to pi so it reports its session.
$XDG_RUNTIME_DIR/wsx/tui-<pid>.sockControl socket for each running dashboard. Without XDG_RUNTIME_DIR, under ~/.local/state/wsx/run/.

Files wsx reads or writes elsewhere

PathUse
~/.claude.jsonClaude Code config. wsx copies MCP servers into worktree entries. See MCP server inheritance.
~/.claude/projects/<encoded-cwd>/<session>.jsonlClaude Code transcripts, read for status, resume and recaps.
~/.claude/skills/, ~/.codex/skills/, ~/.hermes/skills/Where wsx setup install-skill installs skills. See Agent skills.
~/plugins/wsx-claude-commands/, ~/.agents/plugins/marketplace.jsonYour ~/.claude/commands mirrored as a Codex plugin when a Codex agent starts.

Development

Notes for people working on wsx itself.

  • Testing: run the test suite and the checks CI runs.
  • Releasing: cut a release and publish its packages.

Testing

Run the same checks CI runs before you open a pull request:

cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
cargo test --doc --all-features

The suite runs without any coding agent installed. Tests point WSX_CLAUDE_BIN and the other WSX_*_BIN variables at stand-ins such as cat.

Toolchain

rust-toolchain.toml pins the Rust version, so rustup installs the same rustfmt and clippy CI uses. Formatting can differ between Rust releases, so use the pinned toolchain for cargo fmt.

The Nix dev shell (nix develop) provides cargo, clippy, rustfmt, git and gh from nixpkgs and ignores rust-toolchain.toml. If cargo fmt --check passes there but fails in CI, run it with rustup's toolchain instead.

End-to-end checks

harness/ runs a locally built wsx against a throwaway sandbox in /tmp/wsx-test:

bash harness/smoke.sh          # prints SMOKE PASS
harness/harness.sh up          # build wsx and provision the sandbox
harness/harness.sh down        # remove it

See harness/README.md for snapshots and state queries.

Releasing

A git tag drives each release. Merging a version bump to main creates the tag, and the rest is automatic.

Cut a release

  1. In a pull request, set the new version in Cargo.toml, run cargo check to update Cargo.lock, and set the same version in nix/package.nix.
  2. Merge the pull request.

The Tag release workflow (tag-release.yml) runs on each push to main that touches Cargo.toml. If no v<version> tag exists, it tags the merge commit and starts the Release workflow. A Cargo.toml change that keeps the version does nothing.

Tag by hand

If Tag release didn't run or failed, tag the merge commit and push the tag. A tag you push starts Release directly:

git tag v0.2.0 <merge-commit>
git push origin v0.2.0

The tag must match the version in Cargo.toml, or Release stops.

Rebuild an existing tag

If the tag exists but the release is missing or broken, run the Release workflow from the Actions tab with the tag name. It creates the release or replaces its assets.

What Release does

JobDoes
testRuns the CI checks on Linux and macOS. Nothing publishes if they fail.
buildBuilds four targets and packages each as wsx-<version>-<target>.tar.gz with a .sha256.
releaseCreates the GitHub release and uploads the tarballs.
homebrewOpens a pull request that updates Formula/wsx.rb.
RunnerTarget
macos-latestaarch64-apple-darwin
macos-latestx86_64-apple-darwin
ubuntu-22.04x86_64-unknown-linux-gnu
ubuntu-22.04-armaarch64-unknown-linux-gnu

Linux builds use Ubuntu 22.04 so the binaries run on glibc 2.35 and later. The build fails if a binary's architecture doesn't match its target.

Update the formula by hand

Download the release tarballs and their .sha256 files into a directory, then:

scripts/update-homebrew-formula.sh 0.2.0 ./dist

The script changes only the version, URLs and checksums.

Nix

Bump version in nix/package.nix in the same commit as Cargo.toml. If the sessionx revision in Cargo.toml changes, refresh its hash and copy the hash field into outputHashes in nix/package.nix:

nix-prefetch-git --url https://github.com/bakedbean/sessionx --rev <rev>

crates.io

wsx isn't on crates.io. Cargo.toml sets publish = false, and the sessionx git dependency must be published first. After both change, cargo binstall wsx works without --git.