Skip to content

Concepts

wrkstrms (the ws command) runs many terminal programs, most of them AI coding agents, side by side, and keeps track of which one needs you. This page explains the pieces and how they fit together: the server and the client, the workspace, tab and pane hierarchy, workstreams, agents and their statuses, projects, saved sessions, and scripting. Read it once and the rest of the guide will make sense.

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

server (one per name: "default", or -L <name>)
└── workspace "Design System" a group of related work
├── tab "pr-101" often a workstream: worktree + branch
│ ├── pane claude --model opus an agent (status: blocked / done / working / idle)
│ ├── pane nvim --listen ... the tab's Neovim
│ └── pane zsh a shell
└── tab "Button a11y" linked to a project
└── pane codex an agent
projects (plain files on disk, outside the server)
└── button-a11y/ brief.md plan.md decisions.md log.md project.toml

A server holds workspaces. A workspace holds tabs. A tab holds panes in a split layout. Each pane runs one program on its own pseudo-terminal. Some panes are agents. A tab can be a workstream (opened from a template, with its own git worktree and branch) and can be linked to a project (a goal and its notes, kept in files).

ws is split into two processes, the way tmux is:

  • The server owns every pane. It runs each program on a PTY, keeps the terminal emulator for each pane, tracks agent status, saves the session, and draws the whole screen: sidebar, tabs, pane frames, dialogs.
  • The client is what runs in your terminal when you type ws. It is thin: it puts your terminal in raw mode, sends your keys and mouse events to the server, and writes the server’s output to the screen.

Because the server does the drawing, the client has no view state of its own. When you detach (prefix d) or close the terminal, only the client exits. The server and every program in it keep running, and the next ws attaches to the same screen.

Only one client is attached at a time. Attaching from a second terminal takes over, and the first one is told it was detached. The screen takes the size of the attached terminal.

The server is the same ws binary, started in the background in its own session, so a terminal hangup doesn’t reach it. It listens on a Unix socket. Everything else talks to it over that socket: the client, agent hooks, and commands such as ws new, ws open and the scripting commands.

your terminal background server agent CLI in a pane
┌──────────────┐ keys, mouse ┌──────────────────────────┐ PTY ┌────────────────────┐
│ ws (client) │ ───────────────► │ ws __serve │ ◄──────► │ claude / codex │
│ │ ◄─────────────── │ panes, emulators, │ │ │
└──────────────┘ screen output │ status, session saver │ │ hook fires: │
│ │ │ ws report │
other shells │ listens on │ report │ --agent claude │
┌──────────────┐ ws new, ws tab, │ <socket dir>/<name>.sock│ ◄─────── │ Stop │
│ ws <cmd> │ ───────────────► │ │ └────────────────────┘
└──────────────┘ ws pane send... └────────────┬─────────────┘
│ saves as it changes
▼
~/.local/state/ws/state-<name>.db

You can run several independent servers side by side by name, with ws -L <name>. ws --local skips the server and runs everything inside the current terminal. Sessions covers all of this.

Why a server: it owns every terminal, so closing the window you’re looking through doesn’t stop what’s running in it. It also draws the whole interface and streams it to the terminal you attach from, the way tmux does, so any terminal can attach with nothing to install. One terminal is attached at a time; attaching from another takes over the screen.

Panes are real terminals. Each runs one program (claude, codex, nvim, lazygit, a dev server, a shell) with TERM=xterm-256color and COLORTERM=truecolor. ws also sets a few environment variables in every pane (WS_SOCKET, WS_PANE_ID, WS_WORKSPACE, and WS_WORKSTREAM in workstream panes), which is how hooks and scripts find their way back to the server. See Configuration.

Tabs hold panes in a tiled layout. You split, swap, zoom, resize and move panes between tabs. See Panes and layouts.

Workspaces group related tabs, for example one product or one repo family. The sidebar lists them. The first workspace is named after the directory where the server started.

Things close from the bottom up. A pane closes when its program exits. A tab closes when its last pane does, a workspace when its last tab does, and the server stops when its last workspace closes.

A workstream is one unit of work, opened as a tab from a template:

  • a git worktree and branch, created from the template’s repo and base branch (or an existing branch with --branch, or a pull request with --pr);
  • the template’s panes (for example an agent, Neovim and a shell) in the template’s layout;
  • the template’s setup commands (for example pnpm install), run once in a shell pane with the output left visible.
Terminal window
ws new design-system-pr pr-101

Templates are TOML files in ~/.config/ws/templates/. Worktrees go under ~/.local/share/ws/worktrees/<repo>/<name>. A template without a repo makes a scratch workstream with no worktree or branch.

For a workstream with a branch, ws asks gh about the branch’s pull request in the background and shows its number and check status in the status line and on the tab’s chip in the sidebar. prefix p opens the PR in your browser.

Opening a workstream that is already open jumps to its tab. See Workstreams and the template format.

An agent is a pane whose program is an AI CLI. ws gives every agent one of four statuses, ordered by how much it needs you:

Status Mark Meaning
Blocked ● red Waiting on a permission prompt or a question.
Done ✓ green Finished a turn you haven’t looked at yet.
Working ◐ yellow Running, with elapsed time and the current tool.
Idle ○ Finished and seen, or not started.

The status colors the pane’s frame, its tab and its sidebar row. The sidebar lists every agent, most urgent first, and the top bar shows the totals. prefix a jumps to the next agent that needs you. When an agent blocks while you are looking elsewhere, you get a desktop notification.

ws learns about agents in two ways:

  • Hooks give the real status. Claude Code and Codex run a hook at each lifecycle event: session start, prompt submitted, tool use, a permission request or notification, stop, interrupt, session end. Each hook runs ws report, which sends the event to the server through WS_SOCKET, tagged with the pane’s WS_PANE_ID. The server turns events into a status: a submitted prompt or tool use means working, a permission request means blocked, Stop means done. Hooks also carry the session id, model and prompts, which ws uses for labels and for resuming the agent after a restore. You install hooks once with ws integrate claude and ws integrate codex.
  • Process detection finds agents without hooks. Every two seconds, ws looks at the program running in the foreground of each pane. Known agent CLIs (Claude Code, Codex, OpenCode, aider, Gemini, goose, ollama run and others) are listed as agents with “no status” until their hooks report. When a pane goes back to a shell prompt, the agent is gone.

A done agent becomes idle once you look at its pane. When a status looks wrong, the inspector (prefix I, or i on an agent in the sidebar) shows what set it. See Agents.

A project is a goal plus the notes needed to finish it, kept in plain Markdown files that any agent can read and write:

File Holds
brief.md Goal, scope, done-when, constraints.
plan.md A task checklist; the first open task is “next”.
decisions.md What was decided and why.
log.md Handoff entries, one per session, newest last.
project.toml Name, goal, status, workspace, linked workstreams.
context/ Notes for this project only.

Projects live in ~/.local/share/ws/projects/<slug>/, outside the server. They outlive everything else:

  • An agent’s own resume (claude --resume) only works inside one CLI.
  • A workstream’s worktree can be removed.
  • A server can be quit.

The project files survive all three. Work started in Claude Code can be picked up in Codex, or in a fresh Claude session days later, from the same files.

Handoffs carry the work across sessions. ws project continue opens a tab with an agent told to read the brief, plan, decisions and recent log entries, work on the next task, and append a handoff entry to log.md before it stops. ws project handoff asks a project’s agents that aren’t busy to write their entry now. If an agent’s session ends without adding to log.md, ws appends an entry of its own: the agent, model, session id, first and last prompt, duration, and the commits and uncommitted changes since the session began.

Tabs and workstreams can be linked to a project. The sidebar has a Projects section, and the top bar shows the current tab’s project. See Projects.

The server saves the session as it changes: workspaces, tabs, layouts, each pane’s command and current directory, and each agent’s kind, model and session id. It goes to a SQLite file, one per server name.

  • A clean quit (prefix q, ws kill-server, or closing the last pane) clears the saved session.
  • If the server stops without a clean quit (a crash, kill -9, a reboot) or with ws kill-server --keep, the next ws offers to restore it. ws restart restores it straight away. Agents come back resumed (claude --resume <id>, codex resume <id>), Neovim comes back on the same socket, other commands run again, and shells start fresh in the directory they were in.

Scrollback isn’t saved; agents keep their own transcripts. See Sessions.

Everything you can build by hand, you can build from a script. The scripting commands talk to the running server and print what they made, so the next command can build on it:

Terminal window
pane=$(ws tab new --workspace Games --name Snake -- nvim plan.md)
Terminal window
ws split --pane "$pane" -- claude

Inside a pane, these commands go to the server that owns the pane (through WS_SOCKET). Elsewhere they go to the default server, or to a named one with -L <name>. See Scripting and the CLI reference.

  1. You run ws. The client starts the server if none answers on the socket, offering to restore a saved session first, then attaches.
  2. The server starts your shell or command in a pane, with WS_SOCKET and WS_PANE_ID set.
  3. You start claude in the pane. Its hooks run ws report, which sends each event to the server over WS_SOCKET.
  4. The server updates the agent’s status, redraws the frame, tab, sidebar and top bar, records the event for the inspector, and notifies you if the agent blocked while you were elsewhere.
  5. The server saves the session as it changes, so after a crash ws can bring back each pane, resuming agents by session id.
  6. Project files sit outside all of this, so the work can continue in any agent, in any later session.