项目地址:GitHub - AgentDeck
在新的公司里我的工作大概就是一个 AI 产品经理,把所有的需求交给 AI 然后进行不断的迭代验证。这就涉及到了一个多 Agent 管理的问题了,同时公司由于拥有无限量的 Copilot,所以我把其中的 gpt 代理出来到 Codex,Calude 代理出来到 CC,在又一次漏掉某个 Questionnaire 之后,我想出来了一个全新的项目。
我真正想解决的不是“如何把 AI 对话搬到另一块屏幕”,而是一个更窄的问题:当多个 coding agent 同时工作时,我怎样用一眼就能扫完的方式知道谁在运行、谁已经结束、谁在等我,以及下一步应该去哪个终端?
于是有了 AgentDeck:一块 128×64 的 OLED、四个按钮和一块 RP2040 Zero。它监控 Claude Code、Codex、GitHub Copilot CLI 和 OpenCode,把多个原生 CLI 会话放进一个固定的 tmux/psmux workspace,并提供少量刻意受限的控制。
它不是什么
我一开始就给这个项目划了一条边界:AgentDeck 不是迷你聊天客户端,也不是把终端 UI 缩小后塞进 OLED。
它不会显示 prompt、回答、命令参数、文件内容、问题选项、permission payload 或 assistant 正文。真正需要阅读和决策的内容仍然留在 provider 自己的终端里。OLED 只显示适合快速扫视的结构化信息:会话身份、状态、活动类型、token、耗时、原生成本、模型,以及可归因的代码变更统计。
这个边界同时解决了三个问题:
- 128×64 像素根本不适合阅读长内容。
- 问题和审批需要完整上下文,不应该在四个按钮上盲答。
- Bridge 和 OLED 不携带对话正文,降低了泄露私密内容的风险。
因此,AgentDeck 的定位更接近“注意力外设”:它告诉我哪里值得看,但不替我做需要语境的决定。
最终界面只有六行
HOME 页最多显示六行:
1 | OPENCODE 1 session title |
包括了 CLI 的名称和 Session 的名称,Session 的状态,输入输出的 Token 数量,运行时间 + 开销,修改的文件和代码行数量,以及正在使用的模型。
配上四个按钮
四个物理按钮采用 Z 形摆放,但逻辑保持 N 形顺序,这是因为在等待硬件送到的过程中先行架设了 N 形排列而开始了原型开发,但是拿到手却发现是 Z 形的,于是就只能妥协了:
1 | A / Button 1 B / Button 3 |
在 HOME 页,它们分别用于切换上一个会话、切换下一个会话、打开 Actions,以及 Continue。长按 B4 只中断当前 turn,不关闭会话。
Actions 包含 Focus、New、Resume 和 Close。New 会依次选择 provider、workspace 和 SAFE/AUTO 模式;Resume 会选择 provider、历史会话和模式。AUTO 默认禁用,即使启用也会再出现一次明确的危险模式确认。
Alert 页则把 B3 和 B4 固定为 Focus 与 Ignore。Focus 切到原生终端,Ignore 只隐藏当前告警事件。真正的问题回答和权限审批仍然必须在 CLI 中完成。完成提醒优先级最低,ERROR、NEEDS APPROVAL 和 NEEDS INPUT 永远排在它前面。
软件架构:硬件只是最外面一层
1 | RP2040 hardware -> daemon device worker ---+ |
Daemon 是单一协调者,负责会话 registry、当前选择、告警、生命周期和控制。每个 provider CLI 独占一个 tmux/psmux window。Supervisor 只负责在 daemon 退出后按上限退避重启它,daemon 本身不会递归创建 workspace。
RP2040 通过 USB serial 发送四种物理事件,并接收 host 生成的 frame。Bridge 是 loopback-only、带 token 的 newline-delimited JSON 协议,只接受 status、press、button_down 和 button_up。它不会接受“直接关闭某个会话”之类的语义命令。
这套分层也让桌面模拟器和真实硬件共用同一份显示状态。我可以先在 Tk 模拟器中验证 viewport、按钮和字体,再把完全相同的 frame 发给 RP2040。
最难的部分不是焊接,而是四个 provider 都不一样
四个 CLI 没有统一的 telemetry 或 control protocol。AgentDeck 只能为每个 provider 选择最可靠的证据,并在证据缺失时明确降级,而不是猜。
| Provider | 当前证据来源 | 控制能力与限制 |
|---|---|---|
| Claude Code | PID 关联的增量 JSONL transcript | 可在状态被证明安全时使用受限 tmux fallback |
| Codex | 只读 thread catalog 与增量 rollout JSONL | 未协商 app-server 时只使用受限 fallback |
| Copilot CLI | workspace metadata 与增量 event store | 没有协商 SDK control 时不假装支持结构化控制 |
| OpenCode | loopback HTTP API 与只读 SQLite metadata | 可使用文档化的 TUI/session route Continue、Queue 和 Interrupt |
这里有一个重要原则:字段不存在不等于零,接口不支持不等于失败。 OLED 会折叠不支持的指标;如果文件数已知但增删行未知,则显示 F1 +? -?,而不是伪造 +0 -0。
128×64 像素的限制
OLED 左侧只有 104 像素可用于内容,右侧留给四个按钮。最初直接使用 MicroPython 内置字体,但它无法处理中文标题,ASCII 也很难在混合文本中稳定排版。
现在所有文本都由 host 使用 Fusion Bold Pixel 10px 栅格化为 104×9 位图,固件只负责把位图画到 OLED。状态、token、告警和按钮图标则由固件用像素与线段绘制。这样既能显示中文 session title,又能让图标保持清晰。
硬件上的几个坑
1. 按键不是轮询一下就结束了
早期按键处理会出现重复 button-down 和丢边沿。最终固件使用 GPIO 双边沿中断记录候选状态,在主循环中做稳定消抖,并通过固定大小的事件环形队列发送 down/up。IRQ 中只做最少的状态记录,JSON 输出留在主循环。
2. mpremote exec 会打断正在运行的固件
曾经出现过 COM 可以打开、OLED 却一直停在 USB WAIT 的情况。原因不是接线,而是 mpremote exec 用 Ctrl+C 打断了 main.py 并留在 REPL。之后的 host 虽然成功占用了串口,却没有固件主循环消费 frame。
解决方法是执行 reset,然后不要再用 mpremote exec 验证版本;版本应从固件的 ready 消息和 daemon 日志确认。
3. 动画刷新不能重画整块屏幕
State Rail 最初每 160ms 重画整屏,RUNNING 文本又每 500ms 闪烁一次,两个动画会互相阻塞,看起来忽快忽慢。最终固件只更新受影响的 SSD1306 page:状态行覆盖 page 1/2,State Rail 位于 page 7。两个相位都根据绝对时间计算,偶发阻塞后直接追到正确位置,而不是改变长期速度。
远端 Linux 与本地硬件
AgentDeck 曾经支持 host.transport = "ssh",后来我把它删掉了。只把 tmux 命令转发到远端无法正确获得远端 provider 的状态文件、API 和进程证据。
正确拓扑是:daemon、tmux workspace 和四个 provider 都运行在远端 Linux;本地只通过 SSH 把远端 loopback Bridge 转发回来,再由本机 hardware client 驱动 COM 口上的 RP2040。
1 | remote providers + tmux + daemon |
这种方式让 provider evidence 留在它实际产生的机器上,同时 Bridge 仍然不暴露到公网。
哪些设计决定是刻意保守的
- 不显示对话正文。
- 不在四个按钮上回答问题或批准权限。
- 不估算 provider 没有提供的成本或 token。
- 不把未知值写成零。
- 不把 Bash 或外部修改伪装成 agent 的结构化代码变更。
- 不在缺少 capability negotiation 时猜测控制协议。
- 不为了填满 OLED 而保留低价值指标。
这些限制让功能表看起来短了一些,但它们也是我愿意把 AgentDeck 长期开在桌面上的原因。
如何运行
1 | uv sync --extra dev |
RP2040 使用 MicroPython,OLED SDA/SCL 接 GP0/GP1,四个 active-low 按钮接 GP2~GP5。具体安装、配置和安全注意事项见项目 README。
结尾
目前整个项目还是一个比较粗糙的原型,所有电器元件都还只是散落在桌面上,或许之后我也应该设计一个外壳把他们真正组装成一个实体。以及或许着整个项目也不应该只是一个和硬件相关的东西,它也可以作为一个桌面监控软件出现在电脑屏幕当中。
不过我的工作流确实发生了变化,我现在可以同时监控五六个 Session,以及在每个 Session 如果出现消息提示,我可以一键切换过去并进行操作。
AgentDeck 最终没有让我少用终端。它做的是另一件事:让我只在终端真正需要我的时候回去。