Skip to content

Configuration

ws has no settings file of its own yet. You configure it through environment variables, and you define workstreams in template files. This page lists every environment variable ws reads or sets, every file and directory it uses, and the full workstream template format.

The prefix key is Ctrl-Space: prefix e means press Ctrl-Space, let go, then press e. Every key is in Keys and mouse.

Variable Effect
WS_SOCKET_NAME The server name, the same as -L <name>. Picks a separate server with its own socket and saved session. Default: default. See Sessions.
WS_EDITOR The editor for project files (ws project new, the project tab, the workbench layout) and ws mem edit. Wins over everything else.
WS_MEMORY_DB The memory database to use instead of memory.db / memory-<name>.db. See Memory.
VISUAL, EDITOR Used for project files when WS_EDITOR isn’t set and nvim isn’t on your PATH. VISUAL wins over EDITOR.
WS_NO_NOTIFY Set to any value to turn off desktop notifications.
SHELL The program a new pane runs when you don’t give one, and the shell used to resume agents after a restore.
CODEX_HOME Where Codex keeps its files. ws integrate codex writes $CODEX_HOME/hooks.json and $CODEX_HOME/config.toml. Default: ~/.codex.
XDG_CONFIG_HOME Base for templates: $XDG_CONFIG_HOME/ws/templates. Default: ~/.config.
XDG_DATA_HOME Base for worktrees and projects: $XDG_DATA_HOME/ws/. Default: ~/.local/share.
XDG_STATE_HOME Base for the log, saved sessions and crash logs: $XDG_STATE_HOME/ws/. Default: ~/.local/state.
XDG_RUNTIME_DIR When set, sockets go in $XDG_RUNTIME_DIR/ws/.
TMPDIR When XDG_RUNTIME_DIR isn’t set (the usual case on macOS), sockets go in $TMPDIR/ws-<uid>/.
HOME Used to find ~/.claude, ~/.codex, and the defaults above.

The editor for project files is chosen in this order:

  1. $WS_EDITOR
  2. nvim, if it’s on your PATH, even when $EDITOR names something else. (ws is built around Neovim, and an alias such as vim=nvim only exists in an interactive shell.)
  3. $VISUAL, then $EDITOR
  4. vi

Some variables are read by the server, not by the command you type: WS_NO_NOTIFY, SHELL, and the editor variables when you open project files from inside ws. The server has the environment of the terminal where it started. To change one of these, set it and run ws restart from that terminal. The new server gets that terminal’s environment and restores the session; processes in the panes are restarted.

Every program in a pane gets these:

Variable Value
TERM xterm-256color
COLORTERM truecolor
WS_SOCKET The socket of the ws server that owns the pane. Hooks and ws commands run in the pane use it to reach that server.
WS_PANE_ID The pane’s number, for example 12. ws report tags hook events with it, and ws split splits this pane when you don’t pass --pane.
WS_WORKSPACE The name of the workspace the pane was started in.
WS_WORKSTREAM The workstream’s name, in panes opened from a template.

Commands that act on a server use WS_SOCKET when they run inside a pane, unless you pass -L <name>. Outside a pane, they use the default server (or WS_SOCKET_NAME). ws report does nothing unless both WS_SOCKET and WS_PANE_ID are set, which is why agent hooks are harmless in other terminals.

The paths below use the defaults. The XDG variables above move them.

Path What
~/.config/ws/config.toml Settings. Optional; today it holds [review] and [pr_comments] (see Reviewing PRs), [dashboard] (see Getting started) [memory] (see Memory) and [agents.<kind>] (see Memory). A typo in a known section is an error; unknown sections are ignored. The General, Agents & memory and Reviews tabs of Settings (prefix S) edit it for you.
~/.config/ws/keys.toml Your keys: the leader, keys after it, sequences and direct keys. Optional; reloaded while ws runs. See Changing keys, or edit it in Settings › Keyboard shortcuts.
~/.config/ws/templates/*.toml Workstream templates. See Workstream templates.
Path What
~/.local/share/ws/worktrees/<repo>/<name>/ Git worktrees for workstreams. <repo> is the repo directory’s name; a / in the workstream name becomes -.
~/.local/share/ws/memory.db Agent memory; a named server uses memory-<name>.db. See Memory.
~/.local/share/ws/projects/<slug>/ One folder per project: project.toml, brief.md, plan.md, decisions.md, log.md, context/. See Projects.
Path What
~/.local/state/ws/state-<name>.db The saved session and agent event history for the server named <name> (SQLite). SQLite may keep -wal and -shm files next to it.
~/.local/state/ws/ws.log The log for every server and --local run. Past 10 MB it’s moved to ws.log.1 when the next one starts.
~/.local/state/ws/crash-<time>.log A panic message and backtrace, written when the server crashes. <time> is Unix seconds.
~/.local/state/ws/status-report-<pane>-<time>.txt A status report saved by c in the agent status inspector.

The event history keeps the most recent 5,000 events.

The socket directory is $XDG_RUNTIME_DIR/ws/ when XDG_RUNTIME_DIR is set, and $TMPDIR/ws-<uid>/ otherwise. ws creates it readable only by you, since anyone who can reach a socket can drive your agents.

Path What
<socket dir>/<name>.sock The server named <name>; default.sock without -L.
<socket dir>/ws-<pid>.sock A ws --local run.
<socket dir>/nvim-<workstream>.sock The Neovim socket of a workstream’s tab.
<socket dir>/nvim-<pid>-<pane>.sock The Neovim socket of another tab.

A socket that nothing answers on was left by a run that crashed. ws doctor --fix removes them.

ws integrate edits these files. Before it changes a file, it writes a backup next to it, named with the time in Unix seconds. It writes the new file to a temporary name first and renames it into place, so a crash never leaves a half-written file.

Path Changed by Backup
~/.claude/settings.json ws integrate claude settings.json.ws-backup-<time>
~/.codex/hooks.json ws integrate codex hooks.json.ws-backup-<time>
~/.codex/config.toml ws integrate codex, only when the hooks feature is off or unknown, or daemon_auto_start is on or unknown config.toml.ws-backup-<time>

ws won’t edit a hooks file that isn’t valid JSON, or a config.toml that isn’t valid TOML. It stops and tells you to fix the file first.

A template describes how to open a workstream: the repo to make a worktree from, the panes to start, their layout, and setup commands. Put templates in ~/.config/ws/templates/, in files ending in .toml. List the ones ws found with:

Terminal window
ws templates

A file can hold several templates. ws reads the files in name order, and a later template with the same name replaces an earlier one. ws doctor checks every template and warns when a template’s repo isn’t a git checkout.

~/.config/ws/templates/design-system.toml
# Open with: ws new design-system-pr pr-101
[template.design-system-pr]
# The checkout worktrees are made from. A leading ~/ is expanded.
# Leave `repo` out for a scratch workstream (see below).
repo = "~/code/design-system"
# The branch new branches start from. Default: "main".
base = "main"
# The workspace the tab goes in; created if it doesn't exist.
# Default: the template's name.
workspace = "Design System"
# Commands run when the tab opens, joined with &&, in the worktree.
# They run in the first plain shell pane below (cmd = "$SHELL"),
# or in a pane of their own if there is none. Default: none.
setup = ["pnpm install", "pnpm build:tokens"]
# Panes, in order. The first pane gets focus. At least one is required.
[[template.design-system-pr.pane]]
name = "claude" # the pane's label
cmd = "claude" # a shell command, run in the worktree
agent = "claude" # this pane runs an agent CLI
model = "opus" # added as `--model opus`
[[template.design-system-pr.pane]]
name = "nvim"
# A pane whose cmd uses {nvim_socket} becomes the tab's Neovim:
# `ws open` and `prefix e` send files to it.
cmd = "nvim --listen {nvim_socket}"
[[template.design-system-pr.pane]]
name = "shell"
cmd = "$SHELL" # a plain shell pane: runs the setup
[template.design-system-pr.layout]
# main-left: first pane on the left, the rest stacked on the right.
# columns: side by side. rows: stacked. Default: main-left.
preset = "main-left"
# A workstream with no repo: one Codex pane, no worktree or branch.
[template.scratch]
workspace = "Scratch"
[[template.scratch.pane]]
name = "codex"
cmd = "codex"
agent = "codex"
model = "gpt-5" # added as `-m gpt-5`

To start your own, copy the example above into a file in ~/.config/ws/templates/ and edit it.

Each template is a table named [template.<name>]. The name is what you pass to ws new.

Key Type Default Meaning
repo string none The git checkout to make worktrees from. A leading ~/ is expanded. Without it, the workstream is a scratch workstream: no worktree, no branch, and its panes start in the directory the server started in.
base string "main" The branch a new branch is created from.
workspace string the template name The workspace the tab opens in.
setup list of strings [] Commands to run when the tab opens.
pane array of tables required The panes, in order. At least one.
layout.preset string "main-left" main-left, columns or rows.

Layout presets:

Preset Arrangement
main-left The first pane on the left, the rest stacked on the right
columns All panes side by side
rows All panes stacked

Pane keys, in each [[template.<name>.pane]]:

Key Type Required Meaning
name string yes The pane’s label.
cmd string yes A shell command. It runs with /bin/sh -c, with exec in front, in the workstream’s directory.
agent string no The agent CLI this pane runs, such as claude or codex.
model string no The model to start the agent with. For claude it adds --model <model>; for codex, -m <model>. Other agents ignore it.

Unknown keys are an error, so a typo shows up in ws doctor and ws templates instead of being ignored.

cmd can use these placeholders. ws fills them in before it runs the command.

Placeholder Becomes
{worktree} The workstream’s directory: its worktree, or, for a scratch workstream, the directory the server started in.
{branch} The workstream’s branch (empty for a scratch workstream).
{name} The workstream’s name.
{nvim_socket} The tab’s Neovim socket, <socket dir>/nvim-<name>.sock.

When you run ws new <template> <name>:

  1. The name is checked. It may use letters, digits, ., _, - and /, can’t start with . or -, and can’t contain ...
  2. For a template with a repo, ws makes a worktree at ~/.local/share/ws/worktrees/<repo>/<name>:
    • With no options, the branch is <name>. If that branch exists, it is checked out; otherwise it is created from base.
    • With --branch <branch>, that existing branch is checked out.
    • With --pr <number>, ws checks the pull request out with gh pr checkout, forks included. The name defaults to pr-<number>.
    • If the worktree already exists, it is reused.
  3. The panes start in the worktree, each with WS_WORKSTREAM set.
  4. The setup commands run in the first pane whose cmd is exactly $SHELL and that has no agent. If there is no such pane, ws adds a pane named setup for them. The pane prints ▶ ws setup, runs the commands, prints ✓ setup done or ✗ setup failed, and then leaves a shell open so you can read the output.
  5. The panes are arranged by the layout preset. Panes that don’t fit in the window are left out, and the top bar says how many.

If the workstream is already open, ws new jumps to its tab instead. See Workstreams for day-to-day use.