Skip to content

Projects

A project is a goal plus the memory needed to finish it, kept in plain files that any agent can read and write. Work you start with Claude Code can be picked up by Codex, or by a fresh Claude session days later, from the same files. Agent resume only works inside one CLI, and a workstream’s worktree is eventually deleted; a project outlives both. This page covers the project files, how to create, link and continue projects, handoffs, the workbench layout and the PROJECTS sidebar section.

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

Each project is a folder in ~/.local/share/ws/projects/<slug>/ (or under $XDG_DATA_HOME/ws/projects/). The slug comes from the name: lowercase, with runs of other characters turned into -, up to 60 characters. “Button a11y” becomes button-a11y. Commands and scripts refer to a project by its slug.

File Holds
project.toml Name, goal, status, workspace, linked workstreams, and what ws project continue remembers
brief.md Goal, scope, done-when and constraints
plan.md The task checklist
decisions.md What was decided and why, so the next session doesn’t relitigate it
log.md Handoff entries, one per session, newest last
context/ Notes for this project only

Tasks are checklist lines: - [ ] task for open and - [x] task for done (* [ ] works too). Tasks are numbered in order from 1, counting done ones. The first open task is the project’s “next” task. Agents tick tasks as they finish them.

# Plan
- [x] Audit every button for keyboard focus
- [ ] Add a visible focus ring to Button
- [ ] Dropdown keyboard navigation

Here task 2 is next.

ws writes this file; you rarely need to edit it.

Key Meaning
name The display name
goal One line, from --goal
status active, paused or done. Archiving sets done.
workspace The workspace it was created in
workstreams Names of workstreams linked to it
dir Where its agents work; remembered from ws project continue --dir
run The command the workbench’s run pane runs; remembered from --run
run_rows How many rows the run pane starts with; remembered from --run-rows
created The date it was created

From any shell:

Terminal window
ws project new "Button a11y" --goal "Every button keyboard reachable"
Flag Effect
--goal <GOAL> The goal, written to project.toml and the brief
--workspace <WORKSPACE> The workspace to record in project.toml
--no-edit Don’t open the brief afterwards

ws creates the folder with a starting brief, plan, decisions file and log, then opens brief.md in your editor if you’re at a terminal. Fill in the brief and the plan, then continue the project with an agent.

Inside wrkstrms, press prefix N, or choose “New project…” in the main menu (right-click the top bar) or a workspace’s right-click menu. Type a name and press Enter. ws creates the project in the current workspace and opens a tab on it with the brief, plan and log in your editor.

ws picks the editor for project files in this order:

  1. $WS_EDITOR, if set
  2. nvim, if it’s on your PATH
  3. $VISUAL, then $EDITOR
  4. vi

Neovim comes before $EDITOR because wrkstrms is built around it, and because EDITOR=vim with a shell alias vim=nvim would otherwise start the real vim. Set WS_EDITOR to use something else.

A tab linked to a project shows the project’s name in the top bar (◆ Button a11y). When an agent in a linked tab ends its session, ws can write a handoff entry for it (see ws’s own entry), and ws project handoff reaches it.

There are three ways to link:

Terminal window
ws project attach button-a11y # the tab you run this in
ws new design-system-pr pr-101 --project button-a11y # a new workstream
ws tab new --workspace Games --project tetris -- claude # a new tab from a script
  • ws project attach must run in a wrkstrms pane. It links that pane’s tab.
  • When the tab is a workstream, the workstream’s name is also added to workstreams in project.toml.
  • Each command checks that the project exists first.

See Workstreams for ws new and Scripting for ws tab new.

ws project continue opens a tab with an agent that picks the project up from its files:

Terminal window
ws project continue button-a11y
ws project continue button-a11y --agent codex --model gpt-5 --task 4
ws project continue tetris --dir ~/code/tetris --workspace Games --layout workbench --run "cargo run"
Flag Default Effect
--agent <AGENT> claude claude or codex
--model <MODEL> none Passed to the agent as --model <MODEL>
--task <N> the next open task The task number in plan.md to work on
--dir <DIR> see below Where the agent works
--workspace <WORKSPACE> the workspace on screen Put the tab in this workspace. The workspace must exist.
--layout <LAYOUT> agent agent, split or workbench
--split The same as --layout split
--run <COMMAND> With --layout workbench: what the run pane runs, e.g. cargo run
--run-rows <N> half the height With --layout workbench: how many rows the run pane starts with
--focus on without --workspace Move the view to the new tab

Layouts:

Layout Tab
agent One pane: the agent
split A shell on the left, the agent on the right
workbench The project’s files on the left, the agent top right, a run pane below it; see The workbench

How continue behaves depends on whether you pass --workspace, --layout or --split.

Without them, it opens one tab running the agent:

  • Inside wrkstrms, the tab opens in the current workspace. Outside, ws starts (or attaches to) wrkstrms with the tab open.
  • The agent works in --dir, else the directory remembered in project.toml, else the directory you ran the command from. --dir is saved to project.toml.
  • There is no run pane, so --run and --run-rows are ignored, with the warning ws: --run and --run-rows apply to --layout workbench; ignoring them.
  • The tab is named after the project and the task number, e.g. Button a11y · 4.

With any of them, it builds the tab in the running wrkstrms (the one your pane belongs to, or the default server, or -L <name>), and prints the new pane ids:

  • The agent works in --dir, else the directory remembered in project.toml, else the directory you ran the command from.
  • --dir, --run and --run-rows are saved to project.toml, so later runs (and the sidebar’s workbench) reuse them.
  • The tab is named after the project.

In both cases the tab is linked to the project.

ws starts the agent with a first message, the resume prompt. It says:

  • which project it’s continuing and where its files are
  • to read brief.md, plan.md, decisions.md and the last 3 entries of log.md before doing anything
  • its task: task <N>: <text>, or the next open task in plan.md when none is open
  • the directory to work in
  • to tick tasks in plan.md, record decisions in decisions.md, and append a handoff entry to log.md before it stops

For Claude Code, ws also passes standing instructions with --append-system-prompt: that it’s working in the project, where the files are, and the same rules about ticking tasks, recording decisions and writing a handoff. Codex gets the resume prompt only.

Only claude and codex are supported. For any other agent, ws prints the resume prompt so you can start the agent yourself and paste it.

--layout workbench gives a project tab this shape:

┌───────┬───────┬──────────┐
│ brief │ plan │ agent │
├───────┴───────┤ │
│ log ├──────────┤
│ │ run │
└───────────────┴──────────┘
  • Left: the project’s files, opened in the project folder. With a vim-family editor (the command ends in vim), one editor holds all three: brief.md and plan.md side by side on top, log.md across the bottom, with the brief focused. Other editors are given the three files. The editor is chosen as in Which editor.
  • Top right: the agent, continuing the project in its working directory.
  • Bottom right: the run pane, in the same directory.

The run pane runs the project’s run command, such as cargo run, through a login shell. When the program exits, the pane shows its exit code and waits: Enter runs it again, and Ctrl-C leaves you at a shell. Without a run command, the run pane is a shell.

By default the run pane gets half the column. --run-rows 24 starts it at 24 rows and gives the agent the rest, for programs such as terminal games that need a minimum height.

scripts/launch-games.sh in the repo shows the workbench in use. It opens a Games workspace with one workbench tab per game, and skips games that already have a tab, so it’s safe to run twice:

Terminal window
ws workspace new Games --exists-ok
ws project continue tetris --dir ~/code/tetris --workspace Games \
--layout workbench --run "cargo run" --run-rows 24

Each agent session ends by appending a handoff entry to log.md, so the next session knows where things stand. The log starts with the template, inside an HTML comment:

## YYYY-MM-DD HH:MM · <agent> · <model> · <workstream>
Done: what this session finished
State: branch, uncommitted work, whether tests pass
Next: the next task
Open questions: anything the next session should decide or ask

ws reads the ## headings outside comments. The first two parts, the time and the agent, show up in ws project list, ws project show and the sidebar.

Terminal window
ws project handoff button-a11y

This asks every agent in a tab linked to the project that isn’t working to write its entry now. ws types the request into the agent’s pane and presses Enter:

Write your handoff entry now: append it to <log.md path> using the template at its top (Done, State, Next, Open questions). Then stop.

Working agents are skipped. The top bar says how many agents were asked. The command needs a running wrkstrms.

If an agent’s session ends without adding anything to log.md, ws appends an entry itself, from what it knows for certain. A session starts with the first hook event from an agent in a linked tab, and ends when:

  • the agent sends SessionEnd (Codex’s Interrupt only stops a turn, so it doesn’t end the session)
  • the agent exits and its pane goes back to a shell
  • the pane is closed

ws compares the size of log.md with its size when the session started. If it grew, the agent wrote its own entry and ws does nothing. Otherwise it appends an entry like this:

## 2026-09-25 14:10 · claude · opus · pr-101
Written by ws: the session ended without a handoff entry.
Session: 3f2a… (42 min); transcript /Users/you/.claude/projects/…/3f2a….jsonl
First prompt: You're continuing the project "Button a11y". Its files are in …
Last prompt: run the tests again
Commits:
- a1b2c3d Add focus ring to Button
Uncommitted: 2 files changed, 14 insertions(+), 3 deletions(-)
Line Contents
Heading Local time, agent, model, and the workstream (or the workspace, for a tab that isn’t a workstream)
Written by ws How the session ended: ended, exited or was closed
Session Session id, minutes, and the transcript path when known
First prompt, Last prompt The first line of each, up to 120 characters
Commits git log --oneline for commits made in the agent’s directory since the session started, or none
Uncommitted git diff --shortstat, when there are changes

This needs the agent’s hooks (see Agents). An agent without hooks never starts a session, so ws writes no entry for it.

ws also indexes decisions.md, plan.md and log.md into agent memory, so project decisions and handoffs are searchable with everything else (ws mem search) and reach agents with the rest of their context. The files stay the source of truth.

The sidebar’s PROJECTS section, under the spaces, lists projects that aren’t archived. Each row shows:

  • ◆ and the project’s name
  • next: <N> · <task>, or no open tasks
  • at the right, the agent that wrote the last handoff entry

ws re-reads the project folders every 10 seconds, so edits to plan.md and log.md show up on their own.

With the sidebar focused:

Key Action
Enter Go to a tab already on the project, or open a tab with its files
r Rename the project (its slug stays)
x Delete the project and its folder (asks first)
z or Tab Fold or unfold the section

Like AGENTS, the section has its own heading: click it to fold, drag it (═) to resize, and scroll with the wheel. The finder (prefix f) finds projects too; start the query with project to search only projects.

Right-click a project in the sidebar for its menu:

Item Does
Open workbench with Claude A workbench tab in the current workspace: the files on the left, Claude continuing the next open task at the top right, and the run pane below. Claude works in the project’s remembered dir, or in the project folder if there isn’t one. The run pane uses the remembered run and run_rows.
Open files The same as Enter: the project’s tab, or a new tab with its brief, plan and log
Rename project… Asks for a new name (the same as r)
Archive (hide, keep files) Sets the status to done. The project leaves the sidebar; its files stay.
Delete project… Asks, then deletes the folder (the same as x). Tabs linked to it keep running, unlinked.
Terminal window
ws project list # every project: status, next task, last handoff
ws project show button-a11y # details, open tasks and recent handoffs
ws project rename button-a11y "Button accessibility"
ws project rename button-a11y "Button accessibility" --slug
ws project archive button-a11y
ws project delete button-a11y
Command Does
ws project list Lists every project, archived ones included, with its slug, status, name, next task and last handoff
ws project show <slug> Shows the name, slug, status, goal, workspace, linked workstreams and folder; how many tasks are open and the first 5 of them; and how many handoffs there are, with the last 3
ws project rename <slug> <name> Changes the name. If brief.md starts with # <old name>, its title changes too. The slug stays.
ws project rename … --slug Also moves the folder to the new name’s slug. Tabs linked to the old slug in the running wrkstrms follow it.
ws project archive <slug> Sets the status to done: hidden from the sidebar, files kept
ws project delete <slug> Deletes the project’s folder, after asking. --yes skips the question, and is required when there’s no terminal to ask in.

ws project delete only removes a folder that is directly inside the projects folder and has a project.toml.