内容

# 老虎机
从单个 `tmux` 会话中运行多个 Claude Code 代理,彼此独立 - 您只需像调度员一样进行管理。
`sm` 布局每个代理(“工作进程”)一个 git 工作目录(“插槽”),通过 `tmux` 会话建立一个控制窗口(“调度台”)和一个工作进程面板,每个插槽一个,并提供调度员所需的操作:查看哪些是空闲的,委派工作,检查状态,回收已完成的插槽。
它作为一个 CLI (`sm`) 和一个零依赖的 MCP 服务器提供,因此您和您的代理都可以控制它。
## 它解决的问题
每个仓库检出一个 Claude 代理是自然的限制:一个工作目录中的两个代理会相互冲突,分支、索引和构建产物会发生冲突。工作目录解决了隔离问题,但运行多个工作目录会带来一系列新的问题 - 老虎机正是为此而存在的:
- **“哪个插槽是安全的重用?”** 锁文件可能存在问题(其所有者可能已死),一个看似空闲的工作目录可能仍有工作进程正在执行任务,但尚未创建分支。
- **“大家都在做什么?”** 查看多个面板并不扩展,沉默并不意味着空闲 - 慢速工具调用与已完成任务看起来相同。
- **“工作进程如何联系你?”** 抓取面板内容可能会有损失;结果可能会卡在滚动缓冲区中。
- **共享的独占资源** - 身份验证的浏览器、端口 - 会被竞争并被杀死。
工作流程为您带来的好处:一个调度员(您或代理)管理多个工作流程,安全重用基于证据,结构化循环 - 查找容量,委派任务,检查状态,回收 - 而不是手动浏览 `tmux`。
## 思维模型
一个 **会话** 是一层楼,有一个 **调度台** 和一排 **插槽**。**调度员** 坐在调度台;**工作进程** 坐在每个插槽中。调度员永远不会离开调度台,也不会触摸插槽的文件;工作进程永远不会离开他们的插槽。工作流程单向流动(调度台 -> 插槽通过 `sm worker run` / `sm msg send`),结果反向流动(插槽 -> 调度台通过 `sm msg report`,使用 `sm msg inbox` 读取)。
每个插槽都经历以下循环:
```
空闲 -> 被委派任务(锁定,正在进行)-> 打开 PR(等待合并)-> 合并 -> 重置 -> 空闲
```
`sm slot ls` 根据 git 分支和 GitHub PR 状态对每个插槽进行分类,`sm slot reset` 通过将已合并的插槽重置为干净的基础来完成循环。调度员的全部工作是保持循环进行:`sm worker role` 为两个席位(调度台或插槽)打印完整的简介。
## 观点
老虎机是有偏见的。明确的偏见是:
1. **一个插槽,一个工作进程,一条工作流程。** 没有共享,没有重复。
2. **调度员委派和观察 - 从不编辑。** 如果插槽需要修复,其工作进程会进行修复。
3. **空闲是证据,而不是感觉。** 可重复使用性源于分支和 PR 状态;锁定是权威的 _忙碌_,而不是 _空闲_(所有者死亡;`sm lock prune` 确定性地回收陈旧的锁定)。
4. **结构化的反向通道优于面板抓取。** 通过 `sm msg report` 报告是工作进程完成任务的一部分。
5. **共享的独占资源被锁定,而不是竞争。** `sm lock claim browser` 是原子的;失败者会知道谁拥有它,而不是杀死它。
6. **严格的词汇。** 每个概念都有一个确切的名称(如下);如果文档漂移,测试套件会失败。
7. **代理是首要的操作员。** 每个查询和操作都带有 `--json`(交互式会话生成器除外),MCP 服务器镜像 CLI(减去客户端交互式命令:会话创建/附加/分离,预飞行,统计),并且在本地记录使用情况(`sm stats`),因此接口从证据中演变。
## 词汇
严格的定义(`sm help vocab`):
| 术语 | 定义 |
| -------------- | -------------------------------------------------------------------------------------------------- |
| **仓库** | 会话构建所围绕的 git 仓库;一切都源自它 |
| **插槽** | 仓库的一个工作目录;最多容纳一个工作进程和一条工作流程 |
| **工作进程** | 插槽面板中的 Claude 进程;仅执行一个插槽的工作 |
| **调度台** | 会话的第一个窗口;调度员运行会话的席位 |
| **调度员** | 调度台上的角色:查找容量,委派任务,检查状态,回收 - 从不编辑插槽 |
| **会话** | 为一个仓库布局调度台 + 插槽面板的 `tmux` 会话 |
| **锁定** | 对插槽(`.worktree-lock`)或共享机器资源(例如身份验证浏览器)的声明 |
## 安装
需要 Node >= 22 和 `tmux`;`gh`(身份验证)提供 PR 状态分类。
```sh
git clone <此仓库> && cd 老虎机
node bin/sm doctor --fix
```
`doctor --fix` 安全地自动安装所有可自动化的内容:`~/.local/bin` 符号链接(`sm`、`slot-machine`、`slot-machine-mcp`),`tmux` 面板标题块和 MCP 注册 Claude Code (`claude mcp add slot-machine`),然后重新验证并告诉您剩下的内容(例如 `gh auth login`,PATH)。如果您更喜欢手动安装?请自行链接 `bin/*` 并使用 `claude mcp add slot-machine -s user -- ~/.local/bin/slot-machine-mcp` 注册 MCP 服务器。
### 安装(Homebrew)
通过 [tylerbre/homebrew-tap](https://github.com/tylerbre/homebrew-tap) 提供的 `tylerbre/tap` 软件包(`node` 是唯一依赖项;`sm` 是 shell 二进制文件):
```sh
brew install tylerbre/tap/老虎机
```
公式位于 tap 中。要发布版本:`npm run pack`(生成 `slot-machine-vX.Y.Z.tar.gz` 的跟踪文件,仅限 HEAD)-> 将其附加到 `vX.Y.Z` GitHub 版本 -> 在 tap 中更新公式的 `url`/`sha256`(`shasum -a 256 slot-machine-vX.Y.Z.tar.gz`)。
## 快速开始
```sh
sm repo use ~/code/acme # 将 sm 指向仓库;派生/会话/基数从中派生
sm slot create a; sm slot create b; sm slot create c # 设置三个插槽
sm session create # 构建 + 附加会话(调度台 + 工作进程面板)
# 从调度台:
sm slot ls # 谁是空闲的?(git 分支 + PR 状态)
sm worker run "修复 ABC-123: <链接>" # 向第一个空闲工作进程委派任务
sm worker ps # 谁正在工作 / 空闲 / 等待?
sm worker logs a # 深入了解一个工作进程:最后一条消息 + 面板尾部
sm msg inbox # 读取工作进程报告回来的内容
sm slot reset a # 合并?将其重置为干净的基础
sm # 任何时候:跳回最近的会话
```
`sm doctor` 验证整个设置(`tmux`/`git`/`gh`,仓库配置,插槽,面板标题);`sm doctor --fix-tmux` 将推荐的面板标题设置写入您的 `tmux.conf`(一个标记块,安全可重用)并实时应用它们 - 使用多个工作进程面板,边框标题是您区分它们的方式。
## 命令
命令按名词命名,使用类似 docker 的通用动词(`ls`、`inspect`、`create`、`rm`、`kill`):
```
sm doctor 检查环境 + 仓库健康状况
sm stats [--days N] 命令使用情况:计数、错误率、时间
sm help [ns] [cmd] | vocab 概览、命名空间、命令详情或词汇
sm repo ls 已知仓库(当前标记为 *)
sm repo use REPO 选择当前仓库(派生 + 选择)
sm repo inspect [REPO] 一个仓库的解析上下文
sm repo rm REPO 忘记一个仓库(仅配置)
sm session ls 列出仓库正在运行的会话
sm session create [N] [name] [--kill] 构建或附加会话(N = 2|3|4 个面板/窗口)
sm session attach [NAME] 附加/切换(默认:最近;裸 'sm' 也)
sm session detach [NAME] 分离您的客户端(或 NAME 的每个客户端)
sm session reload [NAME] 为创建会话后创建的插槽添加面板
sm session kill NAME... | --all 杀死会话(s);对话在磁盘上存活
sm slot ls [--free|--watch] 分类每个插槽:空闲/合并(可重用)vs 忙碌
sm slot inspect SLOT 深入了解一个插槽:分支、工作进程、锁定、PRs
sm slot focus SLOT | -f 跳转到插槽的 tmux 客户端
sm slot create LABEL [base] | rm LABEL 创建/删除插槽工作目录
sm slot reset SLOT [--force] 将插槽返回到干净的基础分支 @ origin/<base>
sm worker ps [--watch] 每个工作进程:实时/死亡、活动、当前任务
sm worker run MESSAGE [--brief] 向第一个可重复使用的插槽工作进程委派任务
sm worker logs SLOT [-n N] [-f] 深入了解一个工作进程:活动、最后一条消息、面板尾部
sm worker kill SLOT 终止工作进程;其面板恢复为 shell
sm worker role [dispatcher|worker] 打印调度台 -> 插槽的操作模型(自动检测)
sm worker preflight 断言 cwd 是您的插槽工作目录(工作进程,在 git 工作之前)
sm msg send MESSAGE [-s SPEC|-f] 在插槽面板中输入一行(全部、子集或 -f first-free)
sm msg report "MSG" | sm msg inbox 工作进程 -> 调度员反向通道(报告发送,收件箱读取)
sm lock ls 列出持有的资源锁(持有者、年龄、任务)
sm lock claim NAME [task] | release NAME 锁定插槽或共享资源(例如浏览器)/ 释放它
sm lock prune SLOT... | --stale 删除陈旧的工作树锁(已死亡的所有者会话)
```
在大多数命令中添加 `--json` 以获取机器可读的输出;`--repo DIR` 针对一次命令的目标仓库。
### 空闲状态
`sm slot ls` 从每个插槽的 git 分支 + GitHub PR 状态决定可重复使用性(而不仅仅是锁定文件):
| 状态 | 含义 |
| --------------- | --------------------------------------------------------------------------- |
| `free` | 可重复使用 - 在其基准分支上空闲 |
| `merged` | 可重复使用 - 分支的所有 PR 都已合并 |
| `waiting-merge` | 忙碌 - 打开 PR |
| `wip` | 忙碌 - 提交超前基准分支,没有 PR 尚未 |
| `dirty` | 忙碌 - 未提交更改 |
| `closed-pr` | 忙碌 - PR 关闭但未合并 |
| `locked` | 忙碌 - 活动会话持有工作树 |
| `stale` | 忙碌 - 已锁定,但所有者会话已死(使用 `sm lock prune` 回收) |
| `active` | 忙碌 - 活动工作进程正在执行任务(无分支/锁定) |
## 仓库
所有内容都源自仓库的主工作目录,因此 `sm` 是多仓库的:
- **root** = 仓库的父目录(插槽是兄弟)
- **prefix** = `<name>-slot-`(仓库 `acme` -> `acme-slot-`)
- **session prefix** = `<name>`
- **base branch** = 仓库的默认分支(`origin/HEAD`),否则 `main`
设置当前仓库(存储在 `~/.config/slot/config.json` 中);所有命令随后都会对其生效:
```sh
sm repo use ~/code/acme # 派生 + 选择
sm repo use ~/code/foo --prefix wt- --session foo --base master # 覆盖派生值
sm repo ls # 显示当前 + 已知仓库
```
任何命令也接受 `--repo DIR` 以进行一次性仓库操作,无需切换:
```sh
sm --repo ~/code/foo slot ls
```
## MCP
`slot-machine-mcp` 是一个零依赖的 stdio MCP 服务器,包装 CLI - 每个工具都调用 `sm <ns> <cmd> --json`,因此它与 CLI 保持同步。工具镜像 CLI:`sm_repo_ls`、`sm_repo_use`、`sm_doctor`、`sm_session_ls`、`sm_session_kill`、`sm_session_reload`、`sm_slot_ls`、`sm_slot_focus`、`sm_slot_inspect`、`sm_slot_create`、`sm_slot_rm`、`sm_slot_reset`、`sm_worker_ps`、`sm_worker_run`、`sm_worker_logs`、`sm_worker_kill`、`sm_worker_role`、`sm_msg_send`、`sm_msg_report`、`sm_msg_inbox`、`sm_lock_claim`、`sm_lock_release`、`sm_lock_ls`、`sm_lock_prune`.
## 开发
```sh
npm test # node --test(发现 test/)
npm run lint # eslint(@antfu/eslint-config - 一个有偏见的预设;它还负责格式化)
npm run format # eslint --fix(antfu 格式化;没有 Prettier)
npm run pack # slot-machine-vX.Y.Z.tar.gz 的 HEAD(仅跟踪文件)
```
`lib/` 按职责组织,无 barrel 文件 - 从特定模块导入:
- `slots/` - `pure.mjs`(分类/解析,单元测试),`locks.mjs`(一个 lockfile 的模式 + 生命周期,包括嵌入式资源锁),`gather.mjs`(tmux/git/gh 状态)。
- `commands/` - 每个命名空间一个文件(`repo`/`session`/`slot`/`worker`/`msg`/`lock`/`top`)+ `shared.mjs`(跨命令助手)。
- `schema.mjs` + `elevators.mjs` - 零依赖 JSON-Schema 验证器、加载器和版本迁移运行器;模式本身位于 `schema/`(lockfile、config、inbox、usage,每个命令一个)。
- `argspec.mjs` - 单个参数规范适配器,因此 CLI 解析器和 MCP 工具不会漂移。
- `exec.mjs`(tmux/git/gh 管道)、`format.mjs`(输出)、`context.mjs`(仓库解析 + 配置)、`constants.mjs`(配置/数据)、`help.mjs`(用法/词汇文本)、`router.mjs`(调度表)。
`bin/sm` 和 `bin/slot-machine` 是路由器上的薄入口点;`bin/slot-machine-mcp` 是 MCP 服务器,其工具在运行时从 `schema/commands/*.json` 派生。参见 [docs/architecture.md](docs/architecture.md) 获取图表, [CHANGELOG.md](CHANGELOG.md) 获取发布历史。集成测试针对此机器的配置仓库运行,并在没有配置仓库时干净地跳过。