The model
Four nouns. Everything else follows from them.
- Workspace
- 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
- 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
- A cache, deliberately. Branches are the real state. Delete a worktree and reopening the item rebuilds it.
- Session
- 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
| wisp | Go to the picker session, creating it if needed. |
| wisp pick | Run the picker once, here, without a session. |
| wisp open <item> | Open an item directly, no picker. |
| wisp next / prev | Cycle to the next or previous item session. |
| wisp ls | List live sessions. |
| wisp kill <item> | Kill an item's session. Worktrees survive. |
In the picker
- Type to filter.
enteropens the highlighted item. ctrl-ncreates one: type a name, or paste a GitLab link and wisp derives the repo, number and slug from it.ctrl-xkills the highlighted session. It refuses to kill the one you are sitting in, since tmux would take the client down with it.ctrl-rrefreshes the remote items. They are otherwise cached for fifteen minutes.escquits 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.
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.