Skip to main content
Glama
dovahkiin-v

letterbox

by dovahkiin-v

Letterbox

Status: Reference Implementation Python 3.10+ License: MIT POSIX only

📌 为内部生产使用而构建。 架构经过数月日常 AI 开发的验证。作为参考实现开源。

简单来说: 如果你在终端中使用 AI 编程助手,通常一次只能使用一个——而让两个助手协作意味着你自己在窗口之间复制粘贴消息。Letterbox 让两个助手(比如 Claude 和 Gemini,或 Gemini 和 Mistral 的 Vibe)直接互相交谈,并一起完成任务,无需手动操作。

结果: 一个智能体可以规划,另一个可以审查,或者两者分工协作——在你观看的同时自主协作,而不是手动转发每条消息。

一个基于文件的小型通信协议,让两个 AI 智能体在各自的终端中实时对话。

Letterbox 让两个终端编程智能体——Claude Code、Gemini CLI、Antigravity 或 Mistral 的 Vibe——通过共享目录传递消息文件,实现实时对话。当一个智能体发言时,一个 📬 通知会被注入到另一个的终端中,唤醒它读取并回复。无需网络、无需服务器、无需共享内存:只需文件夹中的 JSON 文件和操作系统的原子重命名。它是为内部规划循环构建的消息层,于 2026 年提取为独立的、带版本的工具。如果你曾希望两个 CLI 智能体协作完成任务,而不必在窗口之间复制粘贴,那么这个工具就是为你准备的。它会偶尔根据作者的意愿更新(启动器会告诉你何时有新版本)——但它不受支持:没有路线图,不接受功能请求,也不是社区项目。

该桥接真正跨工具:一边是 Claude,另一边是 Gemini,通过同一通道对话,已经过实际验证。唯一的小问题是设置——Claude 会自动配置,而 Gemini 和 Antigravity 从各自的设置中加载 letterbox。设置 部分会介绍这两种方式。

为什么存在

我每天与两个 AI 协作者一起工作——Claude 和 Gemini——每个都生活在各自的终端工具中(Claude Code、Gemini CLI、Antigravity CLI,现在还有 Mistral 的 Vibe)。Letterbox 是我让它们彼此对话而不是通过我中转的方式。

这有两种模式。有时是手动的:我们在头脑风暴,我想把另一个模型拉进对话。有时是自动的——在规划循环中,Claude 起草计划,每个计划作为内置阶段路由给 Gemini 审查。Letterbox 以相同方式承载两者。

它设计为与工具无关——Claude Code ↔ Gemini CLI ↔ Antigravity CLI ↔ Vibe 任意组合——同模型配对同样有效:两个 Claude 标签页,或两个 Gemini 标签页,通过一个通道对话。

Related MCP server: CC2CC

它是什么

每次 letterbox <harness> 启动都在一个终端内运行两个协调的进程:

  letterbox claude --channel demo --as alice
        │
        ├─ PTY-Parent  (the foreground letterbox process)
        │    • spawns the harness CLI as a PTY child
        │    • watches the channel directory for peer writes
        │    • injects 📬 notifications into the PTY on arrival
        │
        └─ the harness spawns:
             └─ letterbox mcp  (stdio MCP server, agent-spawned)
                  • send_message / check_messages / acknowledge
                  • check_latest_message / channel_info / list_channels

  Both sides coordinate ONLY through the filesystem:

        ~/.letterbox/channels/demo/
          msg-*.json           ← one file per message
          .read/alice.json     ← per-agent read markers
          .read/bob.json

没有守护进程、没有 IPC、没有后台服务。文件系统就是协调媒介——PTY-Parent 的监视器看到新的 msg-*.json 出现并渲染通知;通道目录是持久的、可检查的、可 cat 的。崩溃恢复很简单,因为内存中没有有价值的东西。

智能体获取 letterbox 工具的方式因工具而异,这是你只需配置一次的事情:

  • Claude Code 接受启动标志,因此 letterbox 会自动接线——它生成一个临时的 MCP 配置,并将 --mcp-config 传递给 claude。你无需设置任何东西。

  • Gemini CLI 和 Antigravity 不接受该标志;它们从自己的设置文件中加载 MCP 服务器。你在那里添加一个一行、与通道无关的 letterbox 条目,启动器在启动时通过环境变量将通道和身份传递给每个会话——因此你永远不需要按通道编辑设置。

  • Vibe 从 ~/.vibe/config.toml 加载 MCP 服务器。其 MCP 子进程只继承精简的环境,因此需要一次性桥接脚本,将 LETTERBOX_CHANNEL / LETTERBOX_SENDER 从 Vibe 自身的进程环境转发。一旦就位,任何通道都像 Gemini 一样工作。参见 Vibe 设置 部分。

适用人群

  • 运行终端编程智能体的人,希望在一台机器上进行自主 AI↔AI 对话,无需在窗口之间复制粘贴。

  • 重视文件作为真相来源的人——可审计、可 grep、无晦涩协议、无魔法。

不适用人群

  • 想要托管或网络化聊天服务的人——消息协议仅限文件系统本地,绝不接触网络。(启动器在启动时做一次可选的、尽力而为的版本检查;用 LETTERBOX_NO_UPDATE_CHECK=1 禁用它。)

  • 想要多用户平台的人——它是单台机器上智能体之间的点对点桥接,不是多用户中心(参见为两人构建)。

  • Windows 原生用户——v1 仅限 POSIX(参见我们不支持的内容)。

  • 想要受支持产品的人——letterbox 有版本,偶尔根据作者意愿更新(启动器会告诉你何时有新版本),但没有路线图、没有 SLA、不承诺接受功能请求或持续维护。按原样使用;如果有帮助,拉取更新版本。

为两人构建

Letterbox 本质上是双向桥接——一个对等体与另一个对等体对话是它设计和调优的目标。三个或更多智能体可以共享一个通道:定向寻址(send_message(to="<label>"))和 participants 列表使其可行,同通道广播可到达所有人。但共享通道是广播总线——每条消息唤醒每个参与者。如果没有编排(轮流发言、指定协调者或关于谁何时发言的规则),N 方房间会变成通知风暴,可能很快耗尽模型的消息/使用限制。如果你想要三个或更多,请自带指挥者。底层诚实地显示谁在房间里;礼仪由你负责。

安装

Letterbox 从源码安装(可构建 wheel;目前未发布到 PyPI)。从仓库根目录:

pip install -e .          # or: pip install -e ".[dev]" for the test extras

这会将 letterbox 命令放在你的 PATH 上。该命令必须按名称解析——每个智能体自己会生成 letterbox mcp——因此这是关键。确认它:

which letterbox           # note this absolute path; Gemini/Antigravity setup needs it

你还需要安装你要启动的工具(claude、gemini、antigravity 或 vibe),放在 PATH 上并登录。Letterbox 会为你启动它。

更新

Letterbox 有版本(letterbox.__version__,唯一真相来源);由于没有 PyPI 发布,git main HEAD 就是发布版本。在面向人类的启动时,CLI 会做一次尽力而为的检查(每天最多一次,缓存在 ~/.cache/letterbox/ 下),如果存在新版本则打印一行通知。要更新:

pip install --upgrade "git+https://github.com/dovahkiin-v/letterbox"

这是 letterbox 唯一一次网络调用——消息协议完全保持本地。它使用严格的超时,并且完全静默失败:如果无法访问 GitHub,它只是什么都不打印,绝不会延迟你的启动。它永远不会为 letterbox mcp(智能体的 stdio 服务器)运行。用 LETTERBOX_NO_UPDATE_CHECK=1 完全禁用它。

按工具设置

每个工具只需设置一次。跳过你不会使用的工具。

Claude Code — 无需操作

Letterbox 自动接线 Claude:启动时它写入一个临时的 MCP 配置(模式 0600)并将 --mcp-config <path> 传递给 claude。letterbox 工具出现在该会话中,不会出现在其他地方。没有需要编辑的设置文件。

Gemini CLI — 两个一次性步骤

1. 注册 MCP 服务器 在 ~/.gemini/settings.json 中(如果文件不存在则创建)。使用你安装的 letterbox 的绝对路径(来自上面的 which letterbox),并且只传递 ["mcp"]——没有通道,没有身份:

{
  "mcpServers": {
    "letterbox": {
      "command": "/absolute/path/to/letterbox",
      "args": ["mcp"]
    }
  }
}

这个条目故意与通道无关。 启动器在启动时将 LETTERBOX_CHANNEL、LETTERBOX_SENDER 和 LETTERBOX_INSTANCE_ID 导出到 Gemini 的环境中,MCP 服务器读取它们——因此同一个条目服务于所有通道,你永远不需要再编辑它。(这反映了 Forge 编排器如何通过环境变量传递通道。)

2. 信任你启动的文件夹。 Gemini 拒绝在不受信任的目录中运行,除非有交互式的*“你信任这个文件夹吗?”*提示——而阻塞的 TUI 提示会阻碍自动化。在 ~/.gemini/trustedFolders.json 中预先信任启动目录(或父目录):

{
  "/home/you/projects": "TRUST_PARENT"
}

TRUST_FOLDER 信任恰好该目录;TRUST_PARENT 信任它及其下所有内容,因此一个条目覆盖你所有的项目文件夹。(提示:不要使用 Gemini 的 --skip-trust 标志来绕过这一点——它会强制进行工作区系统提示查找,即使在已信任的目录中也会崩溃。请信任文件夹。)

Antigravity (agy)

以 letterbox agy … 启动它(长形式 letterbox antigravity … 也可以——agy 只是匹配二进制名称的别名)。Antigravity 通过与 Gemini 相同的环境变量接收每次启动的通道和身份;不同之处在于你如何注册 MCP 服务器。agy 从插件加载 MCP 服务器,因此你将 letterbox 安装为一个小型本地插件(一个包含两个 JSON 文件的目录):

# 1. Build the plugin (one directory, two files). Use the absolute
#    `letterbox` path from `which letterbox`.
mkdir -p ~/.letterbox/agy-plugin/letterbox
cat > ~/.letterbox/agy-plugin/letterbox/plugin.json <<'JSON'
{ "name": "letterbox", "version": "1.0.0",
  "description": "Letterbox file-based AI-to-AI comms bridge." }
JSON
cat > ~/.letterbox/agy-plugin/letterbox/mcp_config.json <<'JSON'
{ "mcpServers": { "letterbox": {
    "command": "/absolute/path/to/letterbox", "args": ["mcp"] } } }
JSON

# 2. Install it (and confirm).
agy plugin install ~/.letterbox/agy-plugin/letterbox
agy plugin list

mcp_config.json 与通道无关,原因与 Gemini 的设置条目相同——启动器在启动时通过环境传递通道和身份。与 Gemini 一样,agy 也受文件夹信任限制:它遵循 ~/.gemini/antigravity-cli/settings.json 中的 trustedWorkspaces 列表,因此如果启动目录不在其中,请添加。

状态: PTY 层(通知 + 消息传递,双向)已实际验证,上述插件安装干净地接线了工具。完整的 agy 工具往返刚刚可用且经过轻度测试——将 Antigravity 视为三者中最新的,如有任何异常请报告。

Vibe (Mistral)

以 letterbox vibe … 启动它。Vibe 从 ~/.vibe/config.toml 加载 MCP 服务器,但其 MCP 子进程只继承精简的环境(HOME、PATH、SHELL、TERM、USER、LOGNAME)——因此 LETTERBOX_CHANNEL 等无法通过正常继承到达它。一个小的一次性桥接脚本通过在生成时从 /proc 读取 Vibe 的进程环境来解决这个问题。之后,任何通道都像 Gemini 一样工作——无需按通道编辑配置。

1. 安装桥接脚本(随 letterbox 提供):

cp "$(python3 -c 'import letterbox.data, pathlib; print(pathlib.Path(letterbox.data.__file__).parent / "vibe-mcp-bridge.sh")')" \
   ~/.letterbox/vibe-mcp-bridge.sh
chmod +x ~/.letterbox/vibe-mcp-bridge.sh

2. 在 ~/.vibe/config.toml 中注册它。 将任何现有的 letterbox 条目替换为:

[[mcp_servers]]
name = "letterbox"
transport = "stdio"
command = "/home/YOU/.letterbox/vibe-mcp-bridge.sh"
args = []

使用你的实际主目录路径(不是 ~——Vibe 可能不会展开它)。该条目故意与通道无关:桥接脚本在运行时从 Vibe 的进程环境读取通道和身份,就像 Gemini 从其环境读取一样。

3. 完成。 任何通道都有效:

letterbox vibe --channel blueberry-fields --as mistral

Vibe 还以 --yolo(自动批准)启动,因此注入的通知可以唤醒它,而不会阻塞在每个工具提示上。

注意: 桥接脚本使用 /proc/$PPID/environ 读取 Vibe 的环境——仅限 Linux,这与 letterbox 的 POSIX-only 立场一致。macOS 支持需要不同的机制(ps -p $PPID -Ewww);目前未提供。

状态: 📬 唤醒注入已确认可用(STEP 0 已验证 Vibe 的 ChatTextArea 会覆盖 Enter 键来提交,因此标准 PTY 注入路径适用)。将 Vibe 视为四个中最新的一个,如有任何异常请报告。

快速开始

打开两个终端,各自以不同的身份指向同一个频道。在没有配置文件的情况下,letterbox 的内置默认值提供共享的全局状态目录(~/.letterbox)。

一个真正的跨框架桥接——Claude 与 Gemini 对话(先完成 Gemini 设置):

# Terminal 1
letterbox claude --channel demo --as claude

# Terminal 2
letterbox gemini --channel demo --as gemini

或者两个相同框架的实例,如果你更想保持简单:

# Terminal 1
letterbox claude --channel demo --as alice

# Terminal 2
letterbox claude --channel demo --as bob

两个会话都会启动并安静地等待。现在在终端 1 中轻推一下代理——例如,"给你的对等方发一条消息。" 此后,每条 📬 通知都会唤醒另一个代理来阅读和回复:这种交接就是全部意义所在。--as <label> 名称让对话记录可读;在底层,消息过滤使用的是每次启动的实例 ID,而不是标签。

几点坦诚的说明:

  • 你永远不需要自己运行 letterbox mcp。 该子命令是 stdio MCP 服务器,由框架生成——它是给代理用的,不是给你用的。在终端中手动运行它,它会告诉你这一点然后退出。

  • 启动参数在设计上就是自主的。 Claude 适配器以 --dangerously-skip-permissions 启动,Gemini 适配器以 --yolo 启动,因为注入的消息无法唤醒一个被逐操作审批提示阻塞的代理。如果你不想要这个权衡,letterbox 不适合你——在 letterbox.toml 中覆盖参数,或者干脆不用。

如需完整的分步讲解(两个 Claude 争论热狗是不是三明治),请参阅 examples/two-claudes-debating/ 下的示例项目。

了解桥接状态

由于配置了设置的框架会在每次会话中加载 letterbox,代理可能拥有 letterbox 工具但没有活动的桥接——例如,一个你从未通过 letterbox 启动的普通 Gemini 会话。Letterbox 会冷静地处理这种情况,并给代理一种检查方式:

  • 普通会话是休眠的,不是损坏的。 没有频道时,MCP 服务器仍然会连接(框架会显示平静的"已连接"),但消息工具保持安静——它们只有在实际被调用时才会以清晰、可操作的错误消息失败,绝不会自行触发。有意的普通会话永远不会被垃圾消息打扰;真正配置错误的桥接会在代理尝试通信的那一刻暴露出来。

  • channel_info 是代理的桥接神谕。 调用它会从服务器端回答:桥接是否处于活动状态?在哪个频道上,以什么身份?对等方是谁(从其最近的消息中观察得出),有多少未读消息,以及它最后一次发言是什么时候?不确定自身处境的代理可以在发送前询问——"对等方 90 秒前发言" 与 "从未发言" 读起来截然不同。

观察和列出频道

从任何终端,观察原始对话或查看存在哪些频道:

letterbox tail --channel demo --follow   # stream messages as JSON, one per line
letterbox list-channels                  # list channels with last-activity

要生成一个起始的 letterbox.toml 而不是依赖默认值:

letterbox init --channel demo            # writes ./letterbox.toml (project-local)
letterbox init --global                  # writes ~/.letterbox/config.toml instead

操作

  • 读取会让你赶上进度;收件箱会自动清空。 check_messages 返回未读的对等方消息,并在读取过程中推进该代理的读取标记——因此连续调用会分页浏览积压消息,而清空后的收件箱保持清空状态,无需手动记账。check_latest_message 是一个不推进标记的窥视操作,用于常见的*"他们刚才说了什么?"*场景,acknowledge 则用于显式的单条消息控制。

  • 重启是全新开始,不是重放。 启动时,代理的读取标记会对齐到磁盘上已有的最新消息,因此它只会看到加入之后到达的消息——不会被之前会话的整个频道历史淹没。历史记录仍然存在,可以按需访问(使用 since_id 游标的 check_messages);只是不会强制推送给你。

  • 保留是手动的。 消息一直存在于频道目录中,直到你清理它们;没有自动删除(在通信基础设施中,意外删除是不可接受的)。每个代理的 .read/ 标记跟踪读取状态——它们只推进标记,从不触碰文件,也从不影响对等方的视图。

  • 实际上限:每个频道约 10,000 条未清理消息。 超过这个数量,check_messages 和 list-channels 可能会出现明显的延迟。超过该点请进行清理。

  • letterbox prune 是回收空间的安全方式。 它默认是试运行——只打印将要发生的事情,不触碰任何东西。--yes-i-am-sure 将匹配的文件移动到可逆的 cold/ 子目录;--delete --yes-i-am-sure(双重门控)则永久删除。这是 letterbox 中唯一的破坏性命令。

letterbox prune --channel demo --keep-last 100                   # preview (dry run)
letterbox prune --channel demo --keep-last 100 --yes-i-am-sure   # move to cold/
letterbox prune --help                                           # all selection rules

频道只是一个文件夹,所以 rm -rf ~/.letterbox/channels/demo 也可以——letterbox 不锁定任何东西。

安全模型

完整的威胁模型位于 docs/PROTOCOL.md。简要说明:

  • 频道上的对等代理是不可信的。 其消息正文可能携带提示注入载荷、ANSI 转义序列或 shell 元字符。Letterbox 将双方都视为不可信。

  • 通知仅从可信上下文渲染。 📬 通知模板替换的变量来自观察者自身的配置和观察结果({channel}、{sender}、{message_id}、{timestamp})——绝不来自对等方的消息载荷。恶意对等方可以在其文件中写入任何内容;其中没有任何内容会到达注入的通知。消息正文仅在代理显式调用 check_messages 时才会呈现。channel_info 的对等方字段也是如此:它们是从流量中观察到的信息性内容,绝不会被送入通知。

  • 无执行路径。 Letterbox 从不 exec、eval 或通过 shell 执行消息正文或元数据字段。子进程使用 argv 列表生成(绝不使用 shell=True),且仅用于启动 letterbox.toml 中配置的框架。

  • 路径安全。 频道名称和消息 ID 在任何文件系统操作之前都会经过严格模式验证——../etc 或任何包含斜杠的内容都会被拒绝。

  • 文件系统权限。 ~/.letterbox/ 和频道目录以 0700(仅限用户)创建;生成的 MCP 配置为 0600。

Letterbox 不防御的内容: 被攻破的本地用户账户(文件系统权限是唯一的屏障)、消费框架自身的提示注入漏洞,或跨机器同步(NFS、syncthing)引入的信任边界。它不是静态加密或网络信任层——这些在设计上就不在范围内。

范围与反范围

Letterbox 刻意不做的事情正是重点,而不是缺陷:

  • 无 LLM 调用。 Letterbox 从不调用语言模型、不消耗 token、不持有 API 密钥。通知模板是渲染的文本,不是提示词。

  • 无遥测、无指标、无分析。 不收集任何内容,没有仪表板,没有使用跟踪。

  • 无回传、无自动更新、无版本检查。 Letterbox 从不联系任何服务器。首次运行是静默的。

  • 无网络。 它仅限文件系统本地。跨机器使用是你文件系统同步的事,不是 letterbox 的事。

正是这种反范围让 letterbox 能够保持小巧、惰性、可审计和持久。

可访问性

  • 默认纯文本(--format=plain)——适合管道和屏幕阅读器;tail 向 stdout 输出消息 JSON 供 jq 使用。结构化/彩色输出是可选加入的(--format=rich)。

  • 无纯颜色信号。 --color=auto|always|never 独立控制颜色;颜色绝不是传达状态的唯一方式。

  • stdout 是数据,stderr 是日志——命令可以干净地管道化。

  • 全程 UTF-8。 工具自身的字符串是英文;消息正文可以是任何你书写的语言。

  • 平静的表面。 没有旋转动画、没有遥测横幅、没有升级提示。安静的成

Related MCP Connectors

Related MCP Servers