Skip to content

Commit 03e927e

Browse files
committed
sesh: add svibe --agents CSV mode and --no-specstory opt-out
Heterogeneous svibe layouts via --agents claude,codex,opencode,... (list length = pane count, mixing with positional N rejected for unambiguous semantics). Per-pane wrapping means one opencode pane can sit next to specstory-wrapped claude panes in the same window. Add --no-specstory opt-out to scode/svibe for raw agent invocation without markdown auto-save. Track upstream specstoryai/getspecstory#156 in backlog so opencode joins the auto-wrap list when the PR ships.
1 parent 1bb51cf commit 03e927e

6 files changed

Lines changed: 335 additions & 64 deletions

File tree

TODO.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ with a one-line summary of what shipped.
4747

4848
## P? — Needs evaluation before committing
4949

50+
- [ ] **[?/M] tmux window status indicators (running / idle / error)** — show per-window state on the tab list (e.g. `1 opencode ⚙` busy vs `2 opencode 💤` idle vs `` errored). Three options evaluated; lean toward hook-based per-pane state file driven by opencode `session.*` + Claude `Stop`/`UserPromptSubmit` hooks. Blocked on (a) verifying opencode hook event names, (b) confirming the `dot_claude/modify_settings.json` merger handles multiple entries per event so we don't ping-pong with CodeIsland. → [research](backlog/tmux-window-status-indicators.md)
51+
- [ ] **[?/S] specstory: enable opencode auto-wrap when upstream lands**`_sesh_wrap_agent` in `dot_config/zsh/tools/22_sesh.zsh` currently passes `opencode` through raw because specstory doesn't support it as a provider. Add to the case statement (one-line change) once `specstoryai/getspecstory#156` merges + ships. → [research](backlog/specstory-opencode-support.md)
5052
- [ ] **[?/L] Use mise to manage most runtime versions** — already used for Node (Neovim) and Rust (Cargo) per `README.md`. Question: extend to python/go/ruby and retire ad-hoc PATH wiring + nvm? Trade-off: another layer vs unified version pinning. Spike: pick one runtime currently outside mise (go?), try alongside current setup for two weeks.
5153
- [ ] **[?/L] Optimize zsh & tmux startup time** — needs profiling first (`zprof`, `time zsh -i -c exit`). Current state: conda/nvm are lazy-loaded already, so low-hanging fruit may be gone. Don't optimize blind.
5254
- [ ] **[?/XL] CUDA / ML toolchain ansible role** — driver/cudnn/nccl version matrix is large; only relevant on Linux ML hosts. Defer until there's a concrete host that needs it. Likely belongs behind a new `ml_linux` profile.

backlog/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,7 @@ Add new entries here as you create them. Keep alphabetical.
9696

9797
| Slug | Status | TODO entry |
9898
|---|---|---|
99+
| [`specstory-opencode-support`](specstory-opencode-support.md) | P? deferred | "specstory: enable opencode auto-wrap when upstream lands" |
99100
| [`starship-context-modules`](starship-context-modules.md) | P1 ready | "Starship status-aware modules" |
100101
| [`tmux2k-tuning`](tmux2k-tuning.md) | P1 ready | "tmux2k bandwidth bug" + "tmux2k theme alignment" |
102+
| [`tmux-window-status-indicators`](tmux-window-status-indicators.md) | P? deferred | "tmux window status indicators (running / idle / error)" |
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Add `opencode` to specstory provider auto-wrap list
2+
3+
**Status**: P? deferred — waiting upstream
4+
**Effort**: S (one-line change once upstream lands)
5+
**Related**: `dot_config/zsh/tools/22_sesh.zsh` `_sesh_wrap_agent`,
6+
docs/tools/sesh.md → "Agent wrapping"
7+
8+
## Context
9+
10+
`scode` and `svibe` (and the underlying `_sesh_wrap_agent` helper) auto-wrap
11+
known specstory providers in `specstory run X` to get markdown auto-save
12+
logging. The known-provider list as of 2026-04 is:
13+
14+
```
15+
claude / codex / cursor / droid / gemini
16+
```
17+
18+
`opencode` is intentionally NOT in this list because **specstory itself does
19+
not support it yet**. Calling `specstory run opencode` returns an unknown
20+
provider error, so we currently pass `opencode` through raw — losing the
21+
auto-save markdown that the other agents get.
22+
23+
## Investigation
24+
25+
Upstream tracking (read top to bottom for the latest):
26+
27+
- Issue: <https://github.com/specstoryai/getspecstory/issues/146>
28+
"Add opencode support" — opened by users running opencode locally and
29+
wanting the same markdown capture as the other CLIs.
30+
- PR: <https://github.com/specstoryai/getspecstory/pull/156>
31+
WIP implementation. As of 2026-04 not yet merged. Once merged + released
32+
in a specstory version, `specstory run opencode` should work.
33+
34+
Local check used during this investigation:
35+
36+
```sh
37+
$ specstory run --help
38+
Available provider IDs: claude (Claude Code), codex (Codex CLI),
39+
cursor (Cursor CLI), droid (Factory Droid CLI), gemini (Gemini CLI).
40+
```
41+
42+
Note `opencode` is absent from the available provider list.
43+
44+
## Options considered
45+
46+
| Option | Pros | Cons |
47+
|---|---|---|
48+
| A. Wait for upstream PR #156 to merge, then add `opencode` to the case statement | Zero engineering on our end; consistent with how other providers were added | Blocked on external timeline (unknown ETA) |
49+
| B. Maintain a local shim wrapper that fakes specstory output | We control the timing | Reinvents specstory; output format may diverge from upstream's eventual implementation; high churn |
50+
| C. Use a different recorder for opencode (asciinema, script(1), etc.) | Independent of specstory | Different output format from other agents; breaks the single-source-of-truth markdown story |
51+
52+
Going with A. The cost of waiting is "opencode panes lose the auto-save
53+
nicety"; the cost of B/C is ongoing maintenance plus eventual migration to
54+
upstream.
55+
56+
## Current blocker
57+
58+
Waiting on `specstoryai/getspecstory#156` to merge + ship in a release.
59+
60+
## Activation steps (when unblocked)
61+
62+
When PR #156 merges and is released:
63+
64+
1. Bump local specstory: `npm i -g @specstoryai/specstory@latest`
65+
(or whatever the install command is on this machine).
66+
2. Verify: `specstory run --help` now lists `opencode`.
67+
3. Edit `dot_config/zsh/tools/22_sesh.zsh``_sesh_wrap_agent`:
68+
69+
```diff
70+
- claude|codex|cursor|droid|gemini)
71+
+ claude|codex|cursor|droid|gemini|opencode)
72+
print -r -- "specstory run $agent"
73+
;;
74+
```
75+
76+
4. Update the case-statement comment + the "Agent wrapping (auto)" tables
77+
in:
78+
- `_sesh_wrap_agent` docstring
79+
- `sesh-code` and `sesh-vibe` `--help` output
80+
- `docs/tools/sesh.md``scode` + `svibe` → "Agent wrapping" sections
81+
5. Move this entry from `TODO.md` (P?) to `## Done`.
82+
83+
## Decision
84+
85+
`2026-04 deferred — waiting on getspecstory#156 release`
86+
87+
## References
88+
89+
- <https://github.com/specstoryai/getspecstory/pull/156>
90+
- <https://github.com/specstoryai/getspecstory/issues/146>
91+
- `_sesh_wrap_agent` in `dot_config/zsh/tools/22_sesh.zsh`

docs/tools/sesh.md

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ existing session pointing at the *wrong* repo.
7979
```bash
8080
scode # current repo, default agent (specstory → claude)
8181
scode codex # right pane runs `specstory run codex`
82-
scode opencode # right pane runs `opencode` raw (not a specstory provider)
82+
scode opencode # right pane runs `opencode` raw (not yet a specstory provider)
83+
scode --no-specstory claude # right pane runs `claude` raw (no markdown auto-save)
84+
scode --on-exit kill claude # Ctrl+C closes the right pane (vs default: drops to shell)
85+
scode --on-exit restart codex # codex auto-respawns on crash
8386
scode -p ~/work/foo # explicit repo path
8487
scode --no-attach # build the session in the background, don't switch
8588
scode -h # help
@@ -102,13 +105,42 @@ heavy layout. Use `shere` for bare shells or `svibe` if you really want a
102105
vibe layout in a non-repo directory (svibe also requires a repo, but that's
103106
because vibe layouts make even less sense without git context).
104107

105-
The agent override resolves to:
108+
##### Agent wrapping (auto, opt out with `--no-specstory`)
106109

107110
| Argument | Right-pane command |
108111
|----------|--------------------|
109112
| (none) | `specstory run` (defaults to claude with auto-save md) |
110113
| `claude` / `codex` / `cursor` / `droid` / `gemini` | `specstory run <name>` (known specstory providers) |
111-
| anything else, e.g. `opencode` | runs the binary directly |
114+
| `opencode` and other unknown CLIs | runs the binary directly |
115+
| any agent + `--no-specstory` | runs the binary directly (raw) |
116+
117+
> `opencode` is currently raw because specstory upstream doesn't support it
118+
> as a provider yet. Tracked in
119+
> [`backlog/specstory-opencode-support.md`](../../backlog/specstory-opencode-support.md)
120+
> — when `specstoryai/getspecstory#156` lands, the case statement in
121+
> `_sesh_wrap_agent` gets a one-line update and opencode joins the auto-wrap
122+
> list.
123+
124+
##### `--on-exit` modes
125+
126+
What happens when the agent exits (clean quit, Ctrl+C, crash):
127+
128+
| Mode | Behavior |
129+
|------|----------|
130+
| `shell` (default) | Pane prints a yellow hint and drops into `$SHELL`. You can re-launch the agent (the hint shows the exact command), switch to lazygit, or close the pane manually. Costs one background `$SHELL` process per pane. |
131+
| `kill` | Pane closes when the agent exits. The historical / tmux-default behavior. |
132+
| `restart` | Wraps the agent in `while true; do AGENT \|\| true; sleep 1; done`. Use Ctrl+C twice quickly to break the loop and land in shell. Useful for crash-prone development of an agent itself. |
133+
134+
> The `shell` default trades one background process per pane for far better
135+
> recoverability — the most common reported pain point with the old
136+
> `coding-agent` workflow was "I hit Ctrl+C and lost the pane plus the
137+
> 75/25 layout".
138+
139+
##### Single-agent only
140+
141+
`scode` is single-agent by design. If you want multiple agents in parallel,
142+
use `svibe --agents …`. Passing `--agents` to `scode` errors with a hint to
143+
switch to `svibe`.
112144

113145
> The legacy `coding-agent` named session in `sesh.toml` is **kept for
114146
> back-compat** (still appears in the sesh picker, still works as
@@ -120,29 +152,67 @@ The agent override resolves to:
120152
Built directly with tmux scripting (not tmuxp) because pane counts are
121153
parametric and tmuxp YAML can't express that.
122154

155+
Two ways to specify which agents run:
156+
123157
```bash
124-
svibe # 4× claude (default) + lazygit + nvim
125-
svibe 2 # 2× claude
126-
svibe 4 codex # 4× codex
127-
svibe 6 opencode # 6× opencode (heavy — large monitor recommended)
128-
svibe -p ~/repo 4 claude # explicit path
129-
svibe -h # help
158+
# Homogeneous (positional): all panes run the same agent
159+
svibe # 4× claude (default)
160+
svibe 2 # 2× claude
161+
svibe 4 codex # 4× codex
162+
svibe 6 opencode # 6× opencode (heavy — large monitor recommended)
163+
164+
# Heterogeneous (--agents): one entry per pane, list length = pane count
165+
svibe --agents claude,codex,codex,opencode # 4 panes, mixed
166+
svibe --agents claude,opencode # 2 panes, mixed
167+
svibe --agents 'claude, codex, opencode' # whitespace around commas tolerated
168+
169+
# Other flags (work with both modes)
170+
svibe --on-exit kill 4 claude # Ctrl+C closes panes
171+
svibe --on-exit restart --agents codex,opencode # auto-respawn loop
172+
svibe --no-specstory 4 claude # 4× raw claude (no markdown auto-save)
173+
svibe -p ~/repo --agents claude,codex # explicit path
174+
svibe -h # help
130175
```
131176

177+
Mixing positional `[N] [CLI]` with `--agents` is **rejected** to keep
178+
semantics unambiguous — pick one mode.
179+
132180
Layout (3 windows):
133181

134182
```
135-
window 1 "agents" — N tiled panes, each running AGENT_CLI
183+
window 1 "agents" — N tiled panes, each running its agent (mixed allowed)
136184
window 2 "git" — lazygit (or `git status` fallback)
137185
window 3 "edit" — nvim
138186
```
139187

140-
`N_AGENTS` is bounded `[1, 12]`. Above ~6 the tiled layout becomes too
188+
Pane count is bounded `[1, 12]`. Above ~6 the tiled layout becomes too
141189
cramped on most displays — the cap is conservative, not technical.
142190

143-
Like `scode`, `svibe` refuses outside a git repo. The agent CLI is checked
144-
against PATH up-front; missing → error with the list of available agents
145-
detected on this machine.
191+
##### Validation (fail-fast)
192+
193+
`svibe` validates before building anything:
194+
195+
1. `--agents` and positional `[N] [CLI]` cannot both be set
196+
2. `--on-exit` must be `shell` / `kill` / `restart`
197+
3. Pane count in `[1, 12]`
198+
4. Must be inside a git repo
199+
5. **Every** agent CLI in the list must exist in `$PATH`
200+
201+
This avoids the "4-pane window where one pane is silently dead" failure mode
202+
you'd get if validation were per-pane.
203+
204+
##### `--no-specstory` and `--on-exit`
205+
206+
Same semantics as `scode`. The wrapping is **per-pane**, so you can mix
207+
opencode (raw) with claude (specstory-wrapped) freely:
208+
209+
```bash
210+
# 3 panes — opencode raw, claude wrapped, codex wrapped, all share --on-exit shell
211+
svibe --agents opencode,claude,codex
212+
```
213+
214+
Like `scode`, `svibe` refuses outside a git repo. Use `shere` for plain
215+
shells in arbitrary directories.
146216

147217
### Picking between the four
148218

docs/zsh/aliases.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,8 @@ Quick reference for custom aliases and shell functions defined in this dotfiles
447447
| `sesh-sessions` | function | `dot_config/zsh/tools/22_sesh.zsh` | fzf popup picker for all sesh sessions (also bound to `Alt+S`) |
448448
| `sesh-here` / `shere` | function / alias | `dot_config/zsh/tools/22_sesh.zsh` | Lightweight: bare shell session at `$PWD` (no nvim, no project layout). Pass args/`-c CMD` to override |
449449
| `sesh-root` / `sroot` | function / alias | `dot_config/zsh/tools/22_sesh.zsh` | Connect sesh to current git root (falls back to `$PWD`); honors sesh.toml wildcards/default |
450-
| `sesh-code` / `scode` | function / alias | `dot_config/zsh/tools/22_sesh.zsh` | Repo-scoped coding-agent layout: nvim 75% \| `specstory run [agent]` 25%, plus btop window. Session named `coding-agent/<repo>` (collision-safe). Refuses outside git repos |
451-
| `sesh-vibe` / `svibe` | function / alias | `dot_config/zsh/tools/22_sesh.zsh` | Parametric multi-agent layout: `svibe [N_AGENTS] [AGENT_CLI]` (default `4 claude`). N tiled agent panes + lazygit window + nvim window. Session named `vibe/<repo>`. Refuses outside git repos |
450+
| `sesh-code` / `scode` | function / alias | `dot_config/zsh/tools/22_sesh.zsh` | Repo-scoped coding-agent layout: nvim 75% \| `specstory run [agent]` 25%, plus btop window. Session named `coding-agent/<repo>` (collision-safe). Refuses outside git repos. Flags: `--on-exit shell\|kill\|restart`, `--no-specstory` |
451+
| `sesh-vibe` / `svibe` | function / alias | `dot_config/zsh/tools/22_sesh.zsh` | Parametric multi-agent layout: `svibe [N] [CLI]` (homogeneous) or `svibe --agents claude,codex,opencode,…` (heterogeneous). N tiled agent panes + lazygit window + nvim window. Session named `vibe/<repo>`. Same `--on-exit` / `--no-specstory` flags as scode |
452452
| `try-sesh` / `tsesh` | function / alias | `dot_config/zsh/tools/32_try.zsh` | Open a `try` ephemeral workspace and immediately connect via sesh |
453453

454454
---

0 commit comments

Comments
 (0)