overview
work{spacer} is a local-first control plane and IDE for coding agents: a place to run many long-lived agent sessions side by side, watch every one of them, and review and ship the code they write. You run it on your own machine. Nothing here is a hosted service.
the problem
One claude session is easy. You sit in the terminal it runs in and watch it. That breaks the moment you have more than one going at once. Tabbing between terminals, you lose track of which agent is working, which one is blocked on an approval, and which one finished ten minutes ago and is sitting idle. You can't babysit a whole fleet at the same time, and most of the switching you do doesn't pay off.
work{spacer} keeps the ambient state of every agent in front of you so you only switch when it's worth it. The sidebar shows a per-agent state dot, context %, and token/cost, with an "N need you / N working" header. A Triage Inbox drawer collects the approvals and questions waiting on you across all agents, and you can jump straight to the next agent that needs you. OS notifications fire on needs-approval / needs-input (and optionally on done), suppressed for the agent you're already watching. The point is to make switching a decision you make, not a thing you do constantly out of fear of missing something.
the mental model
The core unit is the agent workspace:
- one agent = one long-lived session = one workspace. Each agent is a single claudemon session, keyed by its working directory. It has its own tabs and panes (the Claude pane, plus terminals, a browser, a git review/diff pane, a per-agent notes scratchpad, and more).
- sessions live in the daemon, not the window. The session runs in
claudemon, independent of any UI. Close the desktop window and the agent keeps running. A session spawned from somewhere else (the MCP facade, another agent) gets auto-adopted and shows up as a card on its own. - one card per session, everywhere. The same agent shows up exactly once whether you're looking from the desktop app, the terminal client, or a remote device, never doubled, never out of sync.
You move between agents in the sidebar, and flip the whole chrome between two UI modes: fleet (the full cockpit, the default) and focus (a distraction-free chat rail with a compact attention badge). Switching modes never remounts your live panes. There's also a Fleet Deck, a cross-agent radar of live agent cards, as a higher-altitude view over the same sessions.
what's running on your machine
You launch one thing, the desktop app, but a few processes run behind it. You never start them by hand; the app does, and it restarts them if they crash.
- the desktop app: the window you click. It's just a client. It doesn't hold your sessions, it displays and drives them.
- the session daemon (
claudemon): a background process that actually owns every agent session and terminal. It keeps running whether or not a window is open, which is why closing the window never kills an agent. - the hub: a background bus that lets other clients, the terminal app, your phone, a second PC, see and drive the same fleet, and that hosts plugins.
You can also drive the same fleet from a terminal with wks-tui, or from a phone/laptop over the network (see remote and multi-client). Because every client talks to the same hub and the same session daemon underneath, a session started in one place can be watched and driven from anywhere else.
getting started
Install work{spacer} and spawn your first agent. The getting started section walks through it. If you're building from source or writing a plugin, see the build & internals page instead.
getting started
This walks through a first run: get the app, spawn your first agent, and find your way around. Building from source and packaging are covered on the build & internals page.
install and launch
Install the desktop app and open it. On first launch it starts its background daemons for you, wires Claude's hooks into ~/.claude/settings.json automatically (that's how the live conversation view, state dots, and approval forwarding light up), and, if there's nothing to restore, shows the session picker, your list of saved sessions, with "new session" to start clean.
One concept to keep in mind from the start: your window is disposable. Agents run in the background session daemon, not in the window. Close work{spacer}, your agents keep working; reopen it (or the terminal app, or your phone) and you reconnect to the same live sessions. The cockpit is detachable from the planes.
spawn your first agent
Hit Ctrl+Shift+N (or the + in the sidebar) to open the spawn dialog. Pick a working directory (the project you want the agent to work in), optionally name it, choose an agent backend (Claude Code, Codex, or OpenCode), and spawn. The agent shows up as a card in the sidebar and opens its workspace. Type in the composer at the bottom and you're off.
Spawn a few more in other directories. Each one is its own workspace with its own tabs and panes. The sidebar shows every agent's live state at a glance, and the "N need you / N working" header tells you who's blocked on a decision. See running agents for every field in the spawn dialog.
find your way around
- Command palette (
Ctrl+Shift+P): a fuzzy launcher for everything, new panes, dialogs, your saved prompts and skills, plugin panes. - Switch agents in the sidebar, or
Ctrl+Tab/Ctrl+Shift+Tab. - Jump to whoever needs you (
Ctrl+Shift+Space), or open the Triage Inbox (Ctrl+Shift+I) to clear approvals and questions across the whole fleet like email. - Fleet Deck (
Ctrl+Shift+F): a cross-agent radar showing every agent at once, with the ones that need you floated to the front. - Keyboard cheatsheet: press
F1any time.
The rest of these docs go pane by pane and feature by feature. A good order from here: running agents → the agent pane → attention and notifications.
running agents
work{spacer} drives four coding agents, not one: Claude Code, Codex, OpenCode, and Pi (beta). You pick the backend when you spawn an agent, and the rest of the app, the sidebar, the Fleet Deck, the inspector, the conversation view, treats them the same way: same state dots, same live transcript, same token/cost readouts, same approval prompts.
the spawn dialog
Open it with Ctrl+Shift+N or the + in the sidebar. It always asks for:
- Working directory: type a path or hit
Browse…. This is the agent's project folder, and the default folder for every pane it opens (its terminals, its git review, etc.). - Name (optional): defaults to the folder name. You can rename it later.
- Agent: pick
Claude Code,Codex,OpenCode, orPi(beta). - Permissions: a permission-mode picker, shared across providers. The shipped default is Full access (bypass permissions), so a fresh agent runs without approval prompts; pick a stricter mode and approvals come to you instead. You can also switch the mode live later, from the composer.
If you pick Claude Code, a few more fields appear:
- Resume session (optional): if you've run Claude in this folder before, you can pick a previous conversation to continue instead of starting fresh. Each is listed by how long ago it ran plus a short summary. The default is to start fresh.
- Model: defaults to
claude.defaultModelfrom config (shipped:opus[1m]); pick aLatestalias, pick a model you've used before, or type a custom id (e.g.opus). - Profile (optional): a saved bundle of model + config dir + extra args + a default set of MCP servers (see configuration).
Defaultif you don't pick one. - MCP servers (optional): tick which MCP servers from your Library to expose to this session. Only the ones you check are loaded.
- Transport:
headless(the default) drives Claude over its stream-json protocol via claudemon — a structured GUI-only session with no terminal view.terminalruns the classic interactive CLI in a PTY, giving you both the GUI and a raw terminal view.
For Codex, OpenCode, and Pi the extra Claude fields drop away. You get a free-text Model (optional) in that provider's own id format (e.g. gpt-5.4 for Codex, anthropic/claude-sonnet-4 for OpenCode; blank = the provider's default), and the same permission-mode picker, whose bypass option is that provider's YOLO mode: on, the agent accepts every approval itself; off, approvals come to you.
Enter in any field spawns; Esc cancels.
terminal-style vs managed agents
There's one difference worth knowing between the backends, in how their pane behaves:
- Claude on the terminal transport has both a rich GUI conversation view and a raw terminal view, and you can flip between them (see the agent pane).
- Everything else is managed — Claude on the default headless transport, Codex, OpenCode, and Pi: the session daemon drives the agent's own protocol directly and renders the conversation, so they're GUI-only, there's no terminal to flip to. They still surface the same state, telemetry, and approval prompts.
Whatever the backend, approvals work the same: when an agent wants to run something it can't on its own, the request comes to you (in the pane, the sidebar card, or the Triage Inbox) unless that agent is in a bypass / YOLO permission mode — which is the shipped default, so tighten the mode at spawn (or live from the composer) if you want to be asked.
agents started elsewhere show up automatically
You don't have to start every agent from the window. A session spawned by a supervisor, by the MCP facade, by the terminal app, or on another machine, gets auto-adopted: it appears as its own card as soon as it's running, deduped so you never see the same session twice across clients. If it was spawned as a child of another agent, it nests under that parent in the sidebar.
supervisor agents
A supervisor is a Claude agent that can see and coordinate the rest of your fleet (it's given the work{spacer} tools). You spawn one from the Ask pane ("ask the fleet"). It renders nested under whatever it's watching, fleet-wide, or scoped to a single agent. Use it for things like a cross-fleet standup, a triage sweep, or a cost rollup. See the Ask pane for the presets.
respawn, terminate, rename
- Respawn restarts a stopped agent with the same folder, backend, profile, model, and settings, and resumes the prior conversation when it can. Its panes re-point at the fresh session.
- Terminate ends the agent's session and removes the card. The pinned Overview workspace can't be terminated. Closing an agent's last tab or pane also terminates it.
- Rename changes the display name (it defaults to the folder name).
Because sessions live in the background daemon, switching between agents never starts or stops one. Only an explicit terminate (or the agent ending on its own) tears a session down.
the agent pane
The agent pane is where you actually talk to a coding agent. Each agent workspace has one, backed by a long-lived claudemon session that lives in the daemon, not in the window. Close the tab, the session keeps running.
It has two surfaces: a rich GUI view and a raw terminal view. A Claude agent on the terminal transport gets both and can flip between them. Everything else — Claude on the default headless transport, Codex, OpenCode, and Pi — runs as a managed session through claudemon's adapters, so it is GUI-only (no PTY to show). Plain terminal-only agents lock to the terminal view.
The toggle lives at the bottom-right of the pane: GUI / Term. It only appears when the provider offers both surfaces. Both views stay mounted at once, so switching never loses your scroll position, your draft, or the PTY.
gui mode vs terminal mode
Terminal mode is the real thing: a live xterm view of the agent's PTY, exactly what you'd see in a normal terminal. Standard terminal keys work, plus:
Ctrl+Ccopies when there's a selection, sends SIGINT when there isn't.Ctrl+V/Ctrl+Shift+Vpaste (bracketed-paste aware).Ctrl+Shift+Ccopies the selection.
GUI mode is a parsed conversation view built from the session's hooks and transcript. Instead of raw scrollback you get:
- Messages as a clean back-and-forth timeline.
- Tool runs collapsed into a work card between the things the agent said. A card reads as "N steps", shows files-changed counts and a
+added −removedline, and expands to the individual steps. The most recent card stays open; older ones collapse. - Inline diffs for
Edit/MultiEdit/NotebookEditand inline read previews, rendered right inside the expanded work card. - A Changed files card at the end of each completed turn: a frozen per-turn summary of what that turn touched, expandable to the diffs.
- Approvals and questions docked where you're about to type (see below).
- A composer at the bottom and an optional inspector rail on the right.
Which view opens first: a pane spawned with a seed prompt always opens in GUI, and a managed (headless / Codex / OpenCode / Pi) session is always GUI. For a terminal-transport Claude pane it honours claude.defaultView, which defaults to terminal. Set the default under Settings → Session, or via the config key claude.defaultView (gui | terminal).
the composer
The composer is the input box at the bottom of the GUI view. It's a multi-line, auto-growing textarea (grows to about 8 lines, then scrolls).
Entersends.Shift+Enterinserts a newline. Those two hints sit right under the box.- The send button (
↑) is shown by default; hide it withui.showComposerSend: falseandEnterstill sends. - Sends go through
claudemon's message endpoint, which writes the whole line atomically and buffers a message sent while the agent is mid-turn or still starting, flushing it the moment the prompt is ready. So you can fire a follow-up without waiting. - Your message shows up immediately (optimistic), before the transcript catches up.
Session pills. Above the box sit three control pills — the model, the reasoning effort, and the permission mode (e.g. "Opus 4.5 ▾ · High ▾ · Full access ▾"). Each is a live switch: change the model or the permission mode mid-session and it takes effect without a respawn, on every provider that supports it.
Attaching files. Three ways, all land as chips above the box:
- Drag and drop anywhere onto the active pane.
- Paste file paths into the composer.
- The
+button (in the composer or the status bar) opens a file picker rooted at the agent's cwd.
Attaching a file auto-switches you to GUI mode. Files are prepended to your message as a path prefix when you send.
Cancel. While the agent is working, a streaming indicator shows with a Cancel button. Esc cancels too (it sends Escape to the session). Cancelling suppresses the streaming UI until the next activity cycle.
the inspector rail
A 320px rail on the right edge of the pane. Toggle it with the PanelRight button in the status bar or with Ctrl+Shift+E (configurable as the toggle-inspector shortcut). Its open/closed state persists across sessions. The rail is a sibling of the content area, so it stays put whether you're in GUI or terminal mode.
Five tabs, each with a count badge:
- Files: every path the agent touched this session, most-recent first, with a
+/~marker (write vs edit) and a×Ntouch count. Click a row to open it in the Review pane. - Plan: the agent's live todo list, with a
done/totalprogress badge. - Flows ("Workflows"), workflow runs, with a "N running" header when any are live.
- Agents: subagents spawned this session.
- Usage: model, a context-window bar, 5-hour / 7-day / monthly rate-limit bars (with reset times when known, plus any rate-limit warning), and totals for input tokens, output tokens, cost, and tool calls.
The rail opens on whatever's active: a running workflow wins, then a running subagent, else Files.
subagent and workflow telemetry
Subagents and workflows are surfaced live from the daemon's workflow watcher, both inline in the timeline and in the inspector rail.
- Subagent rows show a spinner while running (then a
✓), the agent type and description, and per-agenttoolsandtokcounts. While running, the last tool it ran shows on a└line underneath. - Workflow run cards show a
finished/total agentscount, total tokens, and elapsed time. Expand to see the agents, grouped by phase once phase titles land. Each agent row shows a status icon (queued / running / failed / done), model, tokens, and duration, plus its current tool while running. A run auto-collapses when it finishes cleanly; a failed run stays open so you can see which agent failed.
Inline in the timeline, the work card that spawned a run surfaces that run's rich card instead of a flood of raw tool calls. Anything a hook reports before the transcript catches up shows in a live work-log section at the bottom.
The pane's bottom status bar also shows running counts: total tool calls, live subagent count, and attached-file count.
approvals and questions
When the agent needs a permission or asks you something, it surfaces in the needs-you dock pinned right above the composer (not buried in scrollback). The approval gate is enabled per session as soon as it connects, so PreToolUse hooks get parked for the UI to resolve.
- Approvals show the tool name and its input as JSON, with Allow and Deny buttons. One click resolves it.
- Questions (the agent's
AskUserQuestion) render as a picker: numbered option buttons (press the number, or click), multi-select where the question allows it, plus an "Or type a custom answer" box for single questions. Multi-part questions get a stepper with dot progress and a back affordance. There's also a Decline & stop button that cancels the agent's turn instead of answering, for when the question itself is off the rails.
A pending question always wins over an approval, since the agent can fire both in the same turn and the approval is stale once the picker is up. Responses dismiss optimistically so the UI feels instant while the answer round-trips. Every resolved question leaves a persistent answered card in the scrollback — a durable record of what was asked and what you chose (including a decline).
You don't have to be in the pane. The same prompts also appear in the sidebar agent cards and in the top-level Triage Inbox drawer, both reusing the exact same ApprovalPrompt / QuestionPicker so resolution behaves identically wherever you answer. To get to the next agent waiting on you without hunting, press Ctrl+Shift+Space (the next-attention shortcut).
handing off to another agent
Any agent can hand its work to a fresh one, including across providers (Claude → Codex, Codex → OpenCode, whatever). A handoff asks claudemon to build a brief — a structured summary of the task, the state, and the loose ends, written to ~/.workspacer/handoffs/ — either deterministically from the session or authored by the outgoing agent itself. The successor spawns with its composer pre-filled to read the brief, so it starts with context instead of a cold prompt.
pane types
Every agent workspace is a set of tabs, and every tab holds one or more panes. A tab with several panes auto-tiles them. Adding or removing a pane never re-parents the others, so live panes (terminals, agent sessions) keep running. Most panes are opened from the command palette, from a pane's split button, or from a sidebar action. Here is every pane type you can open.
terminal
A real shell in an xterm.js view backed by a PTY in claudemon. The PTY spawns in the agent's working directory by default, so it shares the cwd of whatever agent the tab belongs to. You can set a different shell per pane (shell), and the per-directory script buttons drop a command straight into a fresh PTY (initialCommand).
The PTY lives in the daemon, not the window. Closing the window or switching agents does not kill it. Off-screen terminals stay mounted (their scrollback survives); the disposal-on-hide optimization exists in the code but is gated off by default to avoid losing scrollback.
browser
An embedded Electron <webview>. Two modes:
- Browsing mode (default): a URL bar with back/forward/reload, plus a bookmarks row. Bookmarks and the homepage come from
config.browser(Settings → Browser). Bare hostnames gethttps://prepended. - App mode (
appMode): the chrome is hidden, so the page fills the pane like a standalone app. App-mode (and plugin) webviews get theme injection: the current--wks-*tokens are pushed in viainsertCSS+executeJavaScript, so the embedded page matches your theme. Regular browsing is never restyled.
Hibernation (hibernated) parks a pane so it stops rendering until you focus it. This is renderer-only today. The browser pane is also the substrate for plugin panes (see below).
editor
For reading code an agent just wrote and making quick edits. There are two engines, set in Settings → Editor (config.editor.engine):
codemirror(default) opens the sandboxed editor plugin (workspacer.editor), not a built-in pane. It is a plugin webview rooted at the project. Open it withctrl+shift+d(open-editorhotkey), from the command palette ("Open Editor"), or by clicking a file in the Review pane's tree (which routes throughrequestOpenInEditor). It runs CodeMirror 6 in the webview with: line numbers, active-line highlight, history (undo/redo), multi-selection, bracket matching, auto-close brackets, code folding, selection-match highlight,indentOnInput, and syntax highlighting for js/ts/tsx, py, rs, go, json, css, html, md, c/cpp, java, php, xml, yaml, sql. Keys:Mod-ssave,Mod-pproject search,Mod-btoggle sidebar, plus the standard search keymap (find/replace). It has a lazy file tree, project-wide search (search.project, ripgrep-backed via a hub capability), and external-change detection: the open file is watched (fs.watch), and if the agent rewrites it the editor reloads when clean or prompts when dirty. Plugin settings:vimMode,tabSize(1–8),lineWrap. It reaches the filesystem only through agent-cwd-scoped hub capabilities (fs.read/write/listEntries/watch), not direct disk access.terminalopens your$EDITORin a PTY pane instead. Command defaults tonvim(config.editor.terminalCommand); it runs<cmd> <file>when a file is given, else<cmd> .in the cwd. This is the real-IDE path for heavier work.
(A leftover codemirror editor pane from an old saved session just shows a note pointing you back to the plugin.)
review / diff
A git review surface bound to the agent's cwd. It shows porcelain status split into staged / unstaged / untracked sections in a file tree, with per-file numstat (added/removed lines). Selecting a file renders a unified diff (untracked files render as an all-added diff via --no-index). Very large diffs (over ~1.5M chars) need an explicit click to render.
Mutating git actions run from here against claudemon's git API: stage / unstage (a path, or everything when no path is given), commit (with a message box), and push. Failures (nothing staged, no upstream, conflicts) surface git's stderr verbatim. Click a file to open it in the editor.
notes
A per-agent markdown scratchpad. Whatever you type is persisted onto the pane config and saved with the session, like a browser pane's URL. It survives agent switches and restores on reload (debounced 400ms, flushed on unmount so nothing is lost). Three views via the toolbar: write, split, preview (your choice is remembered). Tab inserts two spaces instead of leaving the textarea. It uses the app's own lightweight markdown renderer, no external editor.
library
CRUD for reusable items you fire into sessions. Four kinds: prompt, skill, agent, mcp. Each item has a scope (global or project), tags, a body, and an action: insert (drop the text into the composer), spawn (start a new agent from it), or copy. MCP items carry a server config (stdio / http / sse, with command/args/env or url/headers) and can be attached to an agent at spawn.
Bodies support variable templating, resolved at insert time:
- Auto context vars:
{{cwd}},{{sessionId}},{{selection}},{{clipboard}}. - Prompt-for-input fields:
{{?Label}}, with a default ({{?Label:default}}) and a type suffix:|text(single line),|area(paragraph, the default),|select:a,b,c(dropdown, first is default),|toggle:on,off(checkbox). These are collected in a small form before the text is used.
There's also a quick-pick library panel (Ctrl+L).
analytics
Cost and token history, read from claudemon's per-session SQLite store. Top tiles show totals: sessions, cost (USD), input/output tokens, tool calls, duration, workflow runs. Below that, breakdown buckets by project, by model, and by day, each with sessions / cost / tokens. Then a list of recent sessions with model, branch, cost, tokens, duration, and status (active / ended). Model names are shortened (claude- prefix and trailing date dropped).
overview
The cross-agent dashboard, lives in the pinned global "Overview" workspace. It shows:
- A rate-limit card: your account-wide 5h and 7d usage windows (percent used + reset time), scanned across all live session snapshots (they're account-global, so it picks the worst one).
- Cross-agent stats and plugin status (the same
sidecar.*health colors as the Plugins Manager). - Favourite and recent directories to spawn into. Star a dir to favourite it; clicking one publishes a
command.spawn_agenton the hub bus to start a new agent there. Favourites/recents live inconfig.directories.
agents / agent watch
Fleet-monitoring panes. The Agents pane is a live timeline of a session's subagents. Clicking a running subagent or workflow in the inspector opens a dedicated watch pane that follows just that run — its status, tools, and tokens — while the parent keeps working. There's also a standalone inspector pane: the same card as the inspector rail, pinnable as a tab (it's what a Fleet Deck card expands into, too).
markdown preview
A read-only rendered view of a markdown file (mdpreview), opened from file links in chat and tool cards. File paths in the conversation are clickable: markdown opens here, anything else opens externally or reveals in the file manager.
ask ("ask the fleet")
Turns a question into a supervisor agent. You type a question (or pick a preset chip: Standup, Triage, Audit, Cost), and it spawns a Claude with the workspacer MCP facade attached so it can observe and coordinate the rest of the fleet. The supervisor is rendered nested under its parent. If the pane was opened scoped to one agent (scopeAgentId), it pre-fills "About \<name\>: " and spawns the supervisor as that agent's child; otherwise it's fleet-wide. Existing supervisors are listed so you can jump back to them. The presets reference each session as session:<id>, which the pane resolves into clickable links.
plugins manager
Lists installed plugins with live sidecar health (healthy/running green, unhealthy amber, crashed red, stopped/disabled grey) fed from sidecar.* bus events. Per-plugin actions: Update, Enable/Disable, Remove. Install new plugins (from a GitHub URL or tarball) via the install dialog.
plugin pane
A generic webview host for a plugin's own UI, built on the browser pane. This is how any plugin surface appears (agent dashboards, the rules-engine editor, the sandboxed editor, etc.). For an agent-scoped pane (it has both a pluginId and a cwd), the pane mints an ephemeral bus token confined to that agent's directory on mount and revokes it on unmount, so the plugin reaches only that project's files instead of the broader static per-plugin token. If there's nothing to scope, or minting isn't available (web build, hub momentarily down), it loads the URL with its baked-in static token, so the webview always loads. Plugins get --wks-* theme tokens and their settings injected.
settings
A searchable settings pane with these sections: Appearance, Layout, Terminal, Keybindings, Notifications, Session, Browser, Editor, Apps, Claude Profiles, Supervisor, Updates, Plugins. All settings persist to config. The search box filters sections by keyword.
bottom terminal drawer
Not a tab pane. A VS Code-style integrated terminal that slides up from the bottom across the full content width (it follows the sidebar's left edge). Toggle it with Ctrl+\ (also a close button; Esc does not close it, since Esc is a real key inside a terminal). Height is 40vh`. It spawns in the current cwd, captured at first open. It's mounted on first open and then kept mounted forever (toggling just slides it off-screen), so the PTY keeps running and scrollback survives.
attention and notifications
You run a lot of agents at once. Most of the time you're looking at one of them. work{spacer} keeps you honest about the rest: every agent's state is visible in the sidebar, the ones that are blocked on you get collected in a single inbox, and the OS taps you on the shoulder when an agent you're not watching needs a decision.
per-agent status in the sidebar
Each agent row in the sidebar carries a live status dot and a small set of numbers, all derived from the agent's session snapshot (the same source the agent pane's status bar uses, so they never disagree).
The dot color tracks the agent's ambient state:
- green: idle
- blue (busy/accent), thinking or streaming (working). The dot pulses (
wks-pulse, 1.6s) and gets a soft glow while working. - amber (warning), waiting on you: needs approval or waiting for input
- faint grey: stopped (no session)
If the agent has an open attention item, the dot is tinted to that item's kind instead and a tiny glyph rides next to it: ! approval, ? question, × error, ◷ stuck, ± big diff, ✓ finished.
Next to the dot is a context bar with a percentage. The number is the share of the context window used, colored green under 70%, amber from 70%, red from 90%. Hover a row and the tooltip spells out the rest: NN% context · N.Nk tok · $N.NN · model. Tokens and cost only show when the daemon has reported them.
the "N need you / N working" header
The "Agents" header in the sidebar shows one status pill:
N need you(amber, pulsing dot) when any agent has an open blocking item. "Need you" counts approval, question, stuck, and error items. Click the pill to open the Triage Inbox.N working(blue) when nobody needs you but agents are still running. "Working" counts agents whose ambient state is thinking or streaming.all clear(green) flashes briefly when you hit jump-to-next-attention and there's nothing waiting.
the triage inbox
The Triage Inbox is a right-side drawer you can open from any agent, so you never have to dig into a workspace to clear a request. Toggle it with ctrl+shift+i (action toggle-inbox), or click the "N need you" header pill.
It's a pure projection of the live attention feed. The feed is built in the renderer from each agent's session snapshot, so items appear and disappear as the underlying session changes. No live panes live in the drawer, so opening it can never remount a terminal, webview, or Claude viewer. Items are sorted most-urgent first by kind priority (approval 100, question 95, error 80, stuck 70, big diff 40, done 20), and within a tier the oldest floats up.
You clear it top-down, like email. The whole drawer is keyboard-driven while it's open and you're not typing in a field:
j/k(or arrows), move the selectiony/Enter, approve the selected approval,n, deny1–9, answer the selected question by option numbero, open that agent's full workspace (drops to piloting, closes the drawer)e, dismiss the items, snooze it (30 minutes, then it resurfaces)Esc, close the drawer
Every action addresses the agent by session id (approve / answer / message / write straight to the daemon), so the inbox can resolve any agent's request without owning that agent's pane. Approvals prefer the daemon's /approve endpoint and fall back to driving Claude's permission menu over the PTY; answers are written to the picker directly. Opening an agent counts as triaging it: that agent's items are cleared from the inbox (the live prompt still shows in its own pane, and any genuinely new request resurfaces later).
When the inbox is empty you get "Inbox zero". The drawer header mirrors the sidebar: "N need you" (amber), else "N to review" (green), else "all clear".
jump to the next agent that needs you
Press ctrl+shift+space (action next-attention) to jump straight to the next agent in the attention feed without opening the inbox. It walks the sorted feed in priority order, starting just after the agent you're on, so pressing it repeatedly cycles through everything that wants your eyes (approvals, questions, stuck, error, done). If nothing needs you, the sidebar header flashes "all clear" for about a second.
OS notifications
When an agent you're not watching changes state, work{spacer} fires a desktop notification so you can babysit a fleet without staring at it. The notifier is driven off the session store on every ambient-state transition and fires at most one notification per meaningful transition:
- needs approval: title
<agent> needs approval - waiting for input: title
<agent> is waiting for input - finished (working → idle), title
<agent> finished, only whennotifyDoneis on
The agent label is the basename of its working directory. Clicking a notification restores and focuses the window and jumps the renderer to that agent.
Suppressed for the watched agent. With onlyWhenUnwatched on (the default), no notification fires for the exact session you currently have on screen with the window focused. The renderer keeps the main process informed of which session is active as you switch agents.
Taskbar flash. On a needs-you event, when the window isn't focused, work{spacer} flashes the taskbar (the Windows "attention" signal). The flash stops as soon as you focus the window.
There is no tray icon or taskbar overlay badge today. Away from the desk entirely, the mobile PWA has background push wired up for the same needs-approval / needs-input alerts, though it's still being stabilized.
notification config
The notifications block in config (Settings ▸ Notifications) has four keys, all booleans:
enabled(defaulttrue), master switch for OS notifications and taskbar flash. UI label "Desktop notifications".notifyDone(defaulttrue), also notify when an agent finishes (working → idle), not just when it's blocked. "Notify when an agent finishes".onlyWhenUnwatched(defaulttrue), suppress notifications for the agent currently on screen. "Only when I'm not watching that agent".sound(defaultfalse), play a sound; otherwise notifications are silent. "Play a sound".
When enabled is off, the other three toggles are disabled in the UI.
remote and multi-client
Because your sessions live in the background daemon rather than in any one window, you can watch and drive the same fleet from more than one place at once: the desktop app, the terminal app, your phone, a second PC, or a full web app in a browser. They all see the same agents, and a layout change on one mirrors to the others like tmux clients on one session. Most of the time you'll just use the desktop app; this section is about reaching your fleet from somewhere else.
remote sharing is opt-in
By default nothing about your fleet is reachable from off your machine, everything stays on localhost. Sharing is a runtime toggle: open Remote control (the phone icon in the Hub status row) and hit Start sharing. Two environment variables cover the launch-time cases:
WORKSPACER_REMOTE_SHARE=1forces sharing on from launch (useful for testing, or a machine that should always share).WORKSPACER_REMOTE_ADDR=host:port(optional) pins the address it listens on (default0.0.0.0:7895). Set it to your tailnet IP so it's only reachable over your private network, not the whole LAN.
When sharing is on, access is gated by a secret token (generated once and stored in your config dir). Anyone with the token-bearing URL gets full control, so treat it like a password.
The intended transport is a Tailscale tailnet, not the open internet. The connection isn't encrypted on its own, so keep both devices on the same trusted private network — or use the one-tap HTTPS via Tailscale option in the share dialog, which fronts the hub athttps://<your-node>.ts.netviatailscale serve.
the share dialog (QR)
Open Remote control from the phone icon in the Hub status row at the bottom of the sidebar.
- Sharing off: a Start sharing button binds the hub to your network on the spot, no relaunch needed.
- Sharing on: it shows a QR code of the ready-to-use URL, point your phone camera at it and tap, plus copy buttons for the URL and the token (masked behind a show/hide eye). Pick what the QR points at with the Full app / Mobile tabs.
- HTTPS via Tailscale: an Enable button runs
tailscale servefor you, so the QR and URLs switch tohttps://<node>.ts.net/…. This is what gives the mobile PWA a secure context, which installing it and push notifications require.
The same URL and token also show in the Hub status row, where the dot turns green once the bus is connected.
the web clients
Three browser clients, nothing to install on the remote device (though the mobile one is installable if you want it on your home screen):
- Mobile (
/m): a phone-first, decision-first client — a live fleet list, a "Needs You" queue of approvals and questions with big approve/answer buttons, per-agent chat, and spawn. It's an installable PWA: add it to your home screen over the HTTPS/Tailscale option. Background push notifications (an alert when an agent needs you, even with the browser closed) are wired up but still being stabilized — don't rely on them yet. This is what the QR points at by default. - Terminal mirror (
/remote): a lightweight client with the agent list, chat, approvals, and a live mirror of the agent's real terminal. Works offline over Tailscale. - Full app (
/app): the complete work{spacer} interface running in the browser, with full desktop parity. Available when sharing is on and a web build is present.
what mirrors, and what stays put
- Layout mirrors. Your agent cards, tabs, and active tab are shared, so every client shows the same arrangement. With one person driving at a time, that just works; the last change wins.
- One card per agent. The same session never doubles up across clients, the desktop, the web app, and your phone all show one card for it.
- Terminals stream on demand. A live terminal is only mirrored to a client while that client is actually watching it, so you're not streaming build logs to a phone in your pocket.
- No window needed. Remote clients keep working even with the desktop app closed, the background daemons serve them on their own.
plugins, mcp, and the tui
There are three ways to push work{spacer} past the built-in panes: install plugins that add new panes and automations, let an agent drive the whole fleet over MCP, or run the terminal client for a keyboard-only cockpit. If you want to write a plugin rather than just install one, that's on the build & internals page.
plugins
Plugins add their own panes (dashboards, editors, rule editors), command-palette entries, and hotkeys. Manage them from the Plugins Manager (command palette → "Manage Plugins").
- Install from a GitHub repo (
owner/repoor a full URL) or a tarball URL, via the install dialog. work{spacer} downloads it, runs any build step, and loads it. - Enable / Disable a plugin without uninstalling it, and Update or Remove it.
- Each plugin shows live health, green when it's running fine, amber/red when it's struggling or crashed, so you can tell at a glance if one is misbehaving.
Plugin panes are themed to match your current theme, and an agent-scoped plugin pane only gets access to that agent's project folder, never your whole disk. A few plugins ship as examples (an event→action rules engine, an agent-grid dashboard, a minimal clock demo); see the build page for the catalog and how to write your own.
Installing a plugin downloads and runs code from the internet, like a VS Code extension. Only install plugins you trust.
let an agent drive the fleet (MCP)
work{spacer} can expose your whole fleet to a coding agent as MCP tools, so an agent (or any MCP client) can list your agents, read a transcript, spawn new agents, send prompts, approve/answer prompts, and post notifications, on your behalf. This is what powers the supervisor agents you spawn from the Ask pane, but you can also attach it to a Claude Code session of your own.
The core tools an MCP client gets:
list_agents, the running agents with their state, usage, and any pending asks.get_transcript, read a session's conversation.spawn_agent/create_terminal, start a new agent or shell.send_message, send a prompt to an agent.approve/answer, resolve a permission prompt or answer a question.signal/terminal_input, interrupt/stop an agent or type into a shell.notify, raise a desktop notification.
That's the driving set; the facade exposes the hub's whole capability surface — around 38 tools in all, covering session snapshots and conversations, config and profiles, saved sessions and layouts, the library, analytics, and path-scoped file/search access. The setup and the exact endpoint to point an MCP client at live on the build & internals page. It keeps working even with the desktop window closed.
the terminal client (wks-tui)
wks-tui is a full terminal cockpit over the same fleet, for when you'd rather stay on the keyboard or you're on a box with no GUI. It sees and drives every agent the desktop app does. Start it standalone, it brings up whatever background daemons it needs on its own, or point it at a remote machine's bus.
- Dashboard + sidebar. A pinned row with agent count, needs-you / working / idle, total cost, and rate-limit windows, plus a sidebar of every live session.
- Per agent. Open one and it has tabs: the agent session (raw terminal by default,
ttoggles the parsed transcript with approve/answer buttons), plus a terminal tab (T), git review (R), and per-project notes (N). - Control.
cspawn,y/n/aapprove/deny/always,1–9answer a question,x/Xinterrupt/stop,erename,Srespawn. - Navigation is vim-first.
Ctrl-Kfor a fuzzy command palette,j/kto move,mto jump to the next agent needing you, aspaceleader/which-key menu,Ctrl-wsplits (tile up to 4 agents), harpoon pins,/to filter, a:command line, and vim counts. Press?for the active bindings. Config (theme + key remaps) lives at~/.config/workspacer/tui.json.
See the build & internals page for how to launch it from source and the daemon/bus flags.
configuration
work{spacer} keeps almost everything in one config directory. Most settings have a UI in the Settings pane, but they all land in plain files on disk you can read, diff, and edit by hand.
where config lives
The desktop app's config dir is resolved like this:
- Linux:
$XDG_CONFIG_HOME/workspacer, falling back to~/.config/workspacer - macOS:
~/.config/workspacer(same XDG fallback) - Windows:
%APPDATA%\workspacer(else~/AppData/Roaming/workspacer)
Everything below lives under that directory:
config.yaml, the main settings file (YAML). Written with defaults on first run, deep-merged with your edits on load.claude-profiles.json, named Claude profiles (see below).plugin-settings.json, per-plugin settings contributed by installed plugins.library/, your reusable prompts/skills/agents (one.mdper item).layouts/, saved layout templates.sessions/, saved sessions (one.yamlper session, used by save/restore and auto-resume).plugins/, installed plugins.remote-token, the hub bus bearer token (created on first run, reused for remote sharing).workspacer.db, SQLite analytics history (per-session model, cost, tokens, tools, branch).
The Claude session daemon claudemon keeps its own SQLite store separately at $XDG_DATA_HOME/claudemon/state.db, falling back to ~/.claudemon/state.db. Override it with claudemon serve --db-path <file>.
config.yaml, the settings file
Edit it directly or use the Settings pane (Ctrl+,). Missing keys fall back to defaults, so a partial file is fine. The notable sections and their defaults:
- ui:
animations: false,theme: dark,mode: fleet(fleet|focus, the UI-mode lens),customThemes(the theme maker's saved themes, keyedcustom:<slug>),fontFamily: "Inter, system-ui, sans-serif",fontSize: 14,borderRadius: 8,navBarHeight: 34,paneHeaderHeight: 22,showComposerSend: true,guiFontScale: 1.15,diffView: stacked(stacked|inline|split).cornerStyleandborderColorare blank by default (use the theme's own). - terminal:
shell: ''(use$SHELL),fontSize: 14,scrollback: 1500,cursorBlink: true,cursorStyle: block.shellsis the picker list (platform-specific defaults). Default terminal font is JetBrainsMono Nerd Font with Caskaydia/Cascadia fallbacks. - browser:
homepage: https://google.com,hibernateAfter: 300(seconds), plus abookmarkslist. - panes:
defaultWidth: 800,gap: 0,peek: 80,insertPosition: after,tabPosition: top(top|left),viewLevel: piloting(piloting|fleet), plus adefaultpane list. - keybindings:
prefix: ctrl+space,chordHints: true, and ashortcutsmap (see keybindings below). - notifications:
enabled: true,notifyDone: true,onlyWhenUnwatched: true,sound: false. - claude:
transport: stream(stream= headless stream-json, GUI-only |pty= the classic terminal CLI),defaultModel: 'opus[1m]',seenModels: [](auto-collected from transcripts to enrich the spawn dropdown),defaultPermissionMode: bypassPermissionsandskipPermissionsDefault: true(a fresh agent runs with full access — tighten these if you want approval prompts by default),defaultView: terminal(gui|terminal, terminal-transport panes only),workLog: cards,settingsOverlay: false(experimental: pass hooks via a private--settingsfile instead of editing~/.claude/settings.json), plus per-sessionbudgets(a cost ceiling that fires a one-shot notification when crossed). - agents:
defaultProvider,defaultCwd, and per-providerbinariesoverrides for the spawn dialog. - supervisor:
model: ''(app default),summarizerModel: sonnet,pollSeconds: 45. - editor:
engine: codemirror(codemirror|terminal),terminalCommand: nvim(the file path is appended as the last arg), optionalvim: truefor CodeMirror. - session:
autoResume: false(restore the most recent session on launch, skipping the picker). - updates:
enabled/channelfor the app's update check (Settings ▸ Updates). - directories:
recent/favouriteslists surfaced in the Overview pane. - scripts: per-directory script buttons keyed by normalized cwd.
- apps: your custom external app/site launcher entries (
name,url,icon).
the Settings pane
Open it with Ctrl+,. There's a filter box at the top. The pane renders these sections, each one writing straight into config.yaml (the plugins section is contributed by installed plugins, and Claude profiles write to claude-profiles.json):
appearance (including the theme maker), layout, terminal, keybindings, notifications, session, browser, editor, apps, Claude profiles, supervisor, updates, plugins.
default keybindings
Bindings are either direct combos (terminal-safe keys) or prefix chords. A chord is the workspace prefix (Ctrl+Space by default) followed by one or more keys. Remap them under keybindings.shortcuts. Direct defaults:
command-palette,ctrl+shift+pnext-agent/prev-agent,ctrl+tab/ctrl+shift+tabnext-attention,ctrl+shift+spacetoggle-ui-mode,ctrl+shift+mspawn-agent,ctrl+shift+nsettings,ctrl+,save-session,ctrl+shift+sopen-file,ctrl+shift+otoggle-help,f1toggle-terminal, `ctrl+``toggle-sidebar,ctrl+shift+btoggle-inbox,ctrl+shift+itoggle-fleet,ctrl+shift+ftoggle-inspector,ctrl+shift+elibrary-picker,ctrl+shift+lopen-review,ctrl+shift+g
Prefix chords (press Ctrl+Space, then the keys): new-terminal (n t), new-claude (n c), new-browser (n b); prev-tab/next-tab (t [ / t ]), move-tab-left/move-tab-right (t , / t .), rename-tab (t r), close-pane (t w); split (p s), quick-split (p c), pane nav nav-left/down/up/right (p h/j/k/l).
On upgrade, an old keybindings schema (the mode/leader style) is migrated once to this prefix scheme. If you had Vim keybinding mode on, it carries over as the editor's vim: true.
Claude profiles
Profiles live in claude-profiles.json and are managed in the Settings pane's Claude profiles section. Each profile has:
nameconfigDir, a customCLAUDE_CONFIG_DIR(blank = the default~/.claude). A leading~is expanded to your home dir at spawn.extraArgs, extra CLI args, e.g.["--dangerously-skip-permissions"].mcpItemIds, library items (kindmcp) to pre-load when spawning with this profile.isDefault
There's always a Default profile; you can't delete it.
claudemon init, hook wiring
For work{spacer} to observe Claude sessions, Claude's hooks POST lifecycle events to claudemon. The desktop app wires this for you: on every launch it runs claudemon init, which merges the hook config into ~/.claude/settings.json. There's nothing to do by hand.
The CLI still exists for headless or custom setups (a machine that only runs the daemon, a nonstandard hook port):
claudemon init # merge hooks into ~/.claude/settings.json
claudemon init --dry-run # print the merged document, write nothing
claudemon init --hook-port 8888 # match a non-default hook port
The default ports the background daemons bind: hook ingress 7890, session API 7891, hub bus 7895, MCP facade 7897. You won't normally touch these unless something else on your machine wants the same port.
environment variables
WORKSPACER_DISABLE_GPU=1, disable GPU rendering. Use this if the window renders broken on Wayland.WORKSPACER_REMOTE_SHARE=1, force remote sharing on from launch. (Sharing is otherwise a runtime toggle: Remote control → Start sharing.) When on, the hub binds off loopback and requires the bus token; off means loopback-only.WORKSPACER_REMOTE_ADDR=host:port, bind address when sharing is on (default0.0.0.0:7895). Pin it to your tailnet IP to avoid exposing it to the whole LAN.CLAUDE_CONFIG_DIR, set per spawn from a profile'sconfigDir, so each profile can point at its own~/.claude-style dir.XDG_CONFIG_HOME, moves the desktop config dir (Linux).XDG_DATA_HOME, moves claudemon'sstate.dbdirectory.
library and analytics on disk
- Library items live as Markdown under
library/in the config dir. The app also reads project-scoped items:<cwd>/.workspacer/library, and Claude's own<cwd>/.claude/skillsand<cwd>/.claude/agents. - Analytics history is a SQLite database at
workspacer.dbin the config dir, one upserted row per session (model, cost, tokens, tools, branch), powering the Analytics and Overview panes. - Sessions are saved as
.yamlfiles undersessions/; withsession.autoResume: truethe most recent one is restored on launch.