wisp  /  a tmux session picker for work items

One work item,
one tmux session.

wisp turns a unit of work into a running workspace. It finds the item, locally or on GitLab, reprovisions the git worktrees it needs, writes the agent's context, and drops you into a session named after it.

› ledger                                                          2/8
 ? ledger-service/318-double-entry-audit     Edit file src/reconcile.ts
  + ledger-service/327-backfill-entries      
                                            Do you want to make this edit?
                                              1. Yes
                                              2. Yes, allow all edits this session
                                              3. No, and tell Claude what to do differently
live ? needs input folder + gitlab enter open ctrl-n new esc quit

The model

Four nouns. Everything else follows from them.

Workspace
$WS
The container. Repo checkouts, docs/, .worktrees/ and the vault sit side by side inside it. Agents start here, so one working directory sees all of them.
Item
<repo>/<iid>-<slug>
A folder in the vault, or _adhoc/<name> for work with no ticket. Its identity is only <repo>/<iid>, because slugs drift between what you typed and what the tracker derives from the title.
Worktree
.worktrees/<repo>--<slug>
A cache, deliberately. Branches are the real state. Delete a worktree and reopening the item rebuilds it.
Session
wisp_<item>
A tmux session tagged @wisp_item. Window 1 is the agent at the workspace root; one further window per worktree, for builds and dev servers.

Nothing wisp does destroys work. Killing a session leaves the worktrees and branches; deleting a worktree leaves the branch.

Usage

wispGo to the picker session, creating it if needed.
wisp pickRun the picker once, here, without a session.
wisp open <item>Open an item directly, no picker.
wisp next / prevCycle to the next or previous item session.
wisp lsList live sessions.
wisp kill <item>Kill an item's session. Worktrees survive.

In the picker

  • Type to filter. enter opens the highlighted item.
  • ctrl-n creates one: type a name, or paste a GitLab link and wisp derives the repo, number and slug from it.
  • ctrl-x kills the highlighted session. It refuses to kill the one you are sitting in, since tmux would take the client down with it.
  • ctrl-r refreshes the remote items. They are otherwise cached for fifteen minutes.
  • esc quits and leaves everything running, agents included.

What sits on disk

wisp reads and writes exactly these paths. Nothing else in the workspace is load-bearing.

$WS/
├── <repo>/                        any directory with a .git
├── docs/<repo>.md                  workspace doc
├── .worktrees/<repo>--<slug>/      the cache, safe to delete
├── .claude/scripts/provision-worktree.sh
└── working_items/                  the vault
    ├── <repo>/<iid>-<slug>/
    │   ├── orchestration.md        manifest, for multi-repo items
    │   ├── notes.md                shown in the preview pane
    │   └── .wisp-context.md        generated on open, safe to delete
    └── _adhoc/<name>/              work with no ticket behind it

Two of these regenerate themselves and never need backing up: .worktrees/ and every .wisp-context.md. Everything else is yours.

Configuration

<workspace>/.wisp.yaml is read first, then ~/.config/wisp/config.yaml, then the environment. The workspace file wins because a workspace owns its own vault, repos and tracker group.

program: claude --permission-mode auto   # runs in window 1
install: false                          # install deps when provisioning
gitlab:
  group: your-group/subgroup
  username: you
  # Recovers the repo directory from a work item URL. Exactly one
  # capturing group, which must be the repo. GitLab nests projects
  # arbitrarily, so there is no generic form for this.
  repo_pattern: '/subgroup/([^/]+)/-/'
  cache_ttl_min: 15

Finding the workspace

In order: WISP_WORKSPACE; then the nearest ancestor holding a .wisp.yaml or a vault directory, so wisp works from inside a repo or a worktree; then a workspace: key in the user config, so it works from anywhere at all. Failing all three it reports what it searched for and names all three fixes.

The manifest

An item can span several repos. YAML frontmatter in orchestration.md is what says so.

---
repos:
  - repo: payments-api
    branch: feature/1042-retry-backoff
    base: main
  - repo: webhooks-worker
    branch: feature/1042-retry-backoff
---

It records intent, and deliberately not worktree paths: a path would be a cache pretending to be state, and a deleted worktree should read as a cache miss rather than as lost work.

Without an orchestration.md, a single-repo item is inferred from the folder's parent with branch feature/<slug>. An _adhoc item gets no repos at all, which is correct: none can be inferred, and the session is notes-only.

Requirements. tmux and git. glab is optional; without it the remote source is simply empty. Provisioning shells out to a provision-worktree.sh in the workspace, which owns branching, environment file copying and dependency installation. wisp does not duplicate that.