Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1. 这本书讲什么

🚧 书还在写:目录已定稿,前言与第2课已上线,其余章节逐模块赶来。

crosschat 是一个命令行工具:它让 AI 会话互相发消息。同一台电脑上的 Claude Code 和 Codex CLI 可以对话,不同电脑上的也可以。

没有它的时候,你要在两个终端之间人工复制粘贴:A 说一段,你复制,切窗口,粘给 B;B 回了话,再复制回来。消息走得通,但每一跳都是你的手,两个 agent 自始至终不知道对面是谁。

这本书教三课,每课回答一个问题:

  1. 一台电脑里,消息怎么从一个 AI 到另一个 AI(第1课 单机通信)
  2. 两台电脑、没有中间服务器时消息怎么走(第2课 跨机通信(ssh))
  3. 一群电脑、只开一个端口时消息怎么走(第3课 跨机通信(tcp))

给谁读:会开终端、会复制粘贴就够了——安装从 Node.js 开始教,不需要编程基础。

不教什么:不教 Claude Code / Codex 自己怎么用(各自官方文档的事);不教 ssh 运维大全(只教跨机用到的那一段)。crosschat 的边界很清楚:只做消息投递,AI 会话的生死归原生命令管。

2. 怎么读

顺序读最省力

教程三课是主线,每课学完一个闭环:第1课跑通单机对话,第2课打通两台机器,第3课组起一张网。每课开头有「学完这课你能做到」和「前置」,课末有「一句话记住」。

课主题要回答的问题
第1课单机通信一台电脑里,消息怎么从一个 AI 到另一个 AI
第2课跨机通信(ssh)两台电脑、没有中间服务器时消息怎么走
第3课跨机通信(tcp)一群电脑、只开一个端口时消息怎么走

全书一条线索:一条消息是怎么送到的。每课把答案的范围扩大一圈——一台电脑、两台、一群。

其余三部怎么用

  • 指南:出了问题查这里,一条一页,给出具体步骤
  • 参考:要查就看,全是表——命令、地址格式、错误码
  • 原理:想弄懂再看,讲设计为什么这样定;不影响使用,可最后读或永远不读

读这本书的前提

  • 一台 Windows 电脑(演示命令以 Windows 为主,Linux 对应命令每步一行带过)
  • 第2课起最好有两台机器;没有第二台,课内给了单机自环的练法
  • 书教最新已发布版 crosschat,内容随仓库更新

3. 符号约定

角色符号

全书对话实录用三个符号,一看即辨:

符号是谁
👤你——操作者,敲命令的人
🤖agent——AI 会话(Claude Code / Codex CLI)
📨agent 收到的外来消息——crosschat 信封

命令实录

实录块长这样(示意):

> crosschat status
本机会话: claude 1 · codex 1

> 开头的行是敲进终端的命令(可以照抄),其余行是运行输出。实录块都带语言标注(console / powershell / bash);图放 text 块,等宽字体下对齐不散。

4. 环境搭建

安装的目标只有一个:跑完本节,crosschat doctor 给出全绿或只有警告的自检报告。doctor 是 crosschat 自带的环境体检——装得对不对、缺什么、下一步该干什么,它一条命令说清。

装 Node.js

crosschat 是 npm 包,先要 Node.js 22 或更高。去 nodejs.org 装 LTS 版(写作时为 22.x),装完验证:

> node --version
v25.0.0

✅ 判据:版本号 ≥ 22。Linux 一行带过:curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs,或用你发行版的 Node 22+ 包。

装 claude CLI

npm install -g @anthropic-ai/claude-code

装完要过账号墙:浏览器登录 Anthropic 账号(或配 API key),按 官方文档 走。最小验证:

> claude --version
2.1.301 (Claude Code)

装 codex CLI

npm install -g @openai/codex

同样过账号墙(ChatGPT 账号或 API key,见 官方文档):

> codex --version
codex-cli 0.160.1

codex 还有一个属于它自己的常驻件——app-server daemon(crosschat 给 codex 投递靠它)。现在不用手动起,doctor 会替你查、缺了会告诉你怎么补。

只装一家可以吗?可以。纯 claude 机器、纯 codex 机器都是合法形态——doctor 对“另一家没装“只给警告不给错误,后面用到哪家再装哪家。

装 crosschat 与 skill

npm install -g @oatelauser/crosschat
crosschat install-skills

第一条装 CLI;第二条把协议教材(skill)装进 claude 与 codex 的技能目录——不装 skill,agent 收到消息不知道怎么回。这是低入侵设计的一半:crosschat 不改 agent 的题词,协议用法由 skill 就地教给每个会话。安装是幂等的,重复跑只会覆盖成同一份内容:

> crosschat install-skills --dir C:\Users\you\AppData\Local\Temp\crosschat-book-demo
installed: C:\Users\you\AppData\Local\Temp\crosschat-book-demo\.claude\skills\crosschat
installed: C:\Users\you\AppData\Local\Temp\crosschat-book-demo\.codex\skills\crosschat

(上面用 --dir 装进了临时目录演示;不加参数默认装进你的用户主目录,输出形似 installed: C:\Users\you\.claude\skills\crosschat 两行。)

验证版本:

> crosschat --version
1.6.4

doctor 自检

用途:一条命令体检全部依赖——Node 版本、两个 CLI、daemon、skill 一致性、身份环境、发件箱。任何一项 ❌ 都会让退出码变 1,所以它也是后面写脚本、装新机时的标准自检。

> crosschat doctor
环境
  ✅ Node v25.0.0 (≥22)
  ✅ claude CLI C:\Users\you\AppData\Roaming\npm\...\claude.exe;可路由会话 4 个
  ✅ codex CLI 0.160.1
  ✅ codex daemon 0.160.0
  ⚠️ claude 侧 skill 与安装源不一致(SKILL.md、references/advanced.md、references/errors.md);重跑 crosschat install-skills
  ⚠️ codex 侧 skill 与安装源不一致(SKILL.md、references/advanced.md、references/errors.md);重跑 crosschat install-skills
  ✅ 无双身份残留
tcp · cloud-broker
  ✅ 节点条目可解析
  ✅ broker 可达(118.196.47.9:7777)
  …
其他
  ✅ 发件箱空(无暂存消息)
结论: 10 项通过,2 项警告,1 项错误

(实录摘自写作机器:tcp 星的几行是第3课的内容,你装完大概率没有这一段;⚠️ 是 crosschat 升级后 skill 没重装的真实样子——照提示重跑 install-skills 即绿。)

隐式发生什么:三级严重度各有含义——✅ 通过;⚠️ 警告(合法状态或需要你顺手做的事,例如 codex 没装、daemon 没跑、skill 旧了,都只提醒不拦你);❌ 错误(这项功能现在用不了,退出码 1)。最容易遇到的一条:⚠️ codex daemon 未运行;要用 codex 通道时先执行: codex app-server daemon start——照抄执行就好:

> codex app-server daemon start
{"status":"started","backend":"pid","pid":35652,...,"cliVersion":"0.160.1","appServerVersion":"0.160.0"}

doctor 与 status 是一对搭档,各答一个问题:doctor 答“环境为什么通/不通“(依赖、版本、配置——查的是因),status 答“现在有谁、消息到没到“(名单、对话、滞留——查的是果)。环境报 ❌ 先 doctor,行为奇怪先 status;两条命令入口处都会顺手补投滞留消息(1.5)。

装完立一个习惯:以后起会话用 crosschat claude / crosschat codex,不裸敲 claude / codex。它们原样转发你熟悉的一切参数,只多做了两件事:让这个会话能收消息(裸启动的 claude 收不到,原因 1.5 claude 腿讲),以及清理容易打架的身份环境变量。环境搭建到此,装备齐了——1.4 第一次对话开始说话。

第1课 单机通信:一台电脑里,消息怎么从一个 AI 到另一个 AI

一台电脑上可以同时跑好几个 AI 会话——🤖 claude 在一个终端里改代码,🤖 codex 在另一个终端里跑任务。它们离得再近也互相听不见:每个会话都是一间隔音室,你说的话只有你终端里的那位听得见。crosschat 让它们按名字互发消息:没有服务器、没有常驻进程,一条命令就是一次完整投递。这一课发起第一次对话、读懂每一条回执——装备在前言的环境搭建里备齐,这也是全书的地基:后面两课的跨机通信,全部建立在本课的机制之上。

学完这课你能做到

  • 完成环境搭建,doctor 全绿
  • 起两个命名会话,让 claude 与 codex 按名字互发消息
  • 读懂回执三态与续话短柄,收到消息后照抄回信、不写任何地址
  • 知道每种信息去哪查:名单用 status、环境用 doctor、账本在 send-log
  • 讲出一条消息在本机的完整旅程,与两条投递腿各自的脾气

前置

  • 会开终端、会装 npm 包;Node.js 都还没装也不要紧,环境搭建从零开始
  • 有 claude 或 codex 至少一个账号(两个都有最顺;只有一个也能走完本课大半——单侧缺席是合法状态,doctor 会这么告诉你)

1.1 背景与场景

两个 AI 为什么说不上话

先说清楚:没有 crosschat 的时候,一台机器上的两个 agent 是怎么协作的。

答案是你当邮差。🤖 A 说了一段话,你复制,切窗口,粘贴给 🤖 B;B 回了话,你再复制回来。消息走得通,但每一跳都是你的手:你不盯着,对话就停;你转述错一个字,两个 agent 就各说各的;而且它们自始至终不知道对面是谁——名字、轮次、上下文,全断在你这里。

crosschat 把邮差换成一条命令。它不造新东西:名字本来就在各家的登记册上(claude 的会话注册表、codex 的线程名册),投递管道本来就在进程之间等着(命名管道、unix socket)。crosschat 做的只是把它们接起来——你敲一条 send,消息从 A 的会话直达 B 的会话,B 那边会把消息当作一条新输入接进去,处理完还能原路回信。

顺带一句:人也能发。你在任何终端里敲 crosschat send,就是一个合法的发送方——给 agent 发任务、发提醒,和 agent 之间互发完全同构。区别只有一个:人没有可投递的会话,所以 agent 不能用 crosschat 给你回信,它的回答就落在它自己的终端里,你去看就行。

没有守护进程的通信

先看“不用它会怎样“:要让两个进程说话,教科书做法是中间架一个消息服务(broker / daemon),所有进程连上去,服务不倒消息不断。代价你也知道——多一个要安装、要启动、要守护、崩溃了全断的东西。

crosschat 拒绝了这条路。它没有自己的守护进程、不监听任何端口、不常驻内存:每条命令都是一个一次性完整状态机——读登记册、解析名字、检查限额、投递、记账,跑完即退。需要跨进程协调的状态(谁和谁在对话、轮次到几了)全部落在磁盘上的小文件里,下一条命令接着读:

┌─────────────────────────── 一台电脑 ───────────────────────────┐
│                                                                │
│  🤖 claude 会话          👤 你(终端)          🤖 codex 线程   │
│  (有自己的注册表)        (随时可发)          (有自己的名册)  │
│         ▲                    │                    ▲           │
│         │                    ▼                    │           │
│         └──────── crosschat send(一次性)─────────┘           │
│                    读名单 · 算轮次 · 投递 · 记账                 │
│                    跑完即退,不驻留                             │
│                                                                │
│  磁盘上的小文件(~/.crosschat/):对话分片 · 发送账本 · 发件箱   │
└────────────────────────────────────────────────────────────────┘

这不是偷懒,是设计边界:消息的“底座“(管道、注册表、会话进程)本来就在,crosschat 只做投递这一件事,能不常驻就不常驻。好处立竿见影——没有“crosschat 挂了“这种故障形态;坏处也明说:它不缓存离线消息、不做历史检索,那些是别的东西的职责(第四部 设计边界展开讲)。

一个例外要提前打招呼:codex 侧的接收依赖 codex 自己的 app-server daemon——那是 codex 生态的常驻件,不是 crosschat 的。1.5 通信机制会看清两条投递腿各靠什么活着。

1.2 术语

术语表

词一句话出现的地方
会话(session)一个运行中的 claude 实例,登记在本机会话注册表里名单、信封署名
线程(thread)codex 侧的对话单位,住在 codex 自己的名册里名单、投递目标
名字会话/线程的人读地址,--to 按它找人(全名精确,或唯一前缀)send --to、名单
短柄 cN本机会话引用的两字符缩写(c21 这样),照抄即路由信封回信命令、回执行尾
信封消息的包装:署名、轮次、回信命令、正文,收方看到的就是它每条到达的消息
轮次(turn)对话的第几来一回,信封里形如 turn="2",带预算时 turn="13/40"信封、回执
投递三态delivered(送达)/ queued(对方忙已入队)/ parked(已暂存自动重投)每条 send 的输出
发件箱parked 消息在本机的暂存队列,看门狗自动重投,满 200 条诚实拒绝parked 回执、排查

会话与线程

两个词说的是同一层东西的两家方言:claude 那边叫会话,codex 那边叫线程。crosschat 不另造统一身份——投递时各用各的原生 id(claude 会话 id、codex 线程 id),名单也只是把两家的登记册拼在一起给你看。所以“会话“与“线程“在本书里可以当同义词读,真到投递那一刻,走的是两条不同的腿(1.5 通信机制)。

名字与短柄

名字是给人看、给 --to 用的:claude 会话在启动时用 -n 命名,codex 线程用 /rename 随时命名。名字可以带空格(命令里加引号),可以只写前缀——只要前缀在名单里唯一命中一个人。没起名的会话也有办法够到(codex 未命名线程用 id8 寻址,1.4会亲眼看到)。

短柄是给 AI 照抄用的:c 加一个数字,就两个字符。每对会话的每段对话在本机有一个柄,信封里的回信命令用的就是它——回信方逐字照抄,不手打、不截断。为什么需要它:完整的对话引用有八十多个字符,让 AI 手抄一遍是最脆弱的环节;两个字符的柄把这件事变成不可能抄错。柄背后的机制(本机分片、同对话跨轮不变、旧柄照发也准)在 1.5 回信怎么认路。

信封与轮次

信封是每条消息到达时的样子:一行署名(谁发的)、一行回信命令(怎么回)、正文、一个闭标签。它不只是格式——回信需要的全部路条就坐在里面,收到信封的会话照抄其中一条命令就能原路回信,不需要知道对面住在哪、叫什么。📨 这个符号在全书里就指它:agent 收到的外来消息信封。

轮次是这段对话的第几来一回。第一封信 turn="1",回信就是 2,再回是 3——两边都不用记,引用里自带计数器,每封信自动 +1。它是对话的里程表:turn 越大,说明这段对话已经往返越多轮。带上预算时形如 turn="13/40",超过预算不硬拦,但提醒你该总结收尾了——轮次是协议给上下文压力的唯一答案,这一点 1.5要专门讲清。

1.3 命令总览

单机通信的命令面一页看完:九个命令每条一句话,本课用到哪几个、深入在哪节,先有张地图。全量旗标与细节住第三部命令总表。

命令一览

命令一句话深入
crosschat send --to <名>给本机某个会话发新消息(同名自动接续最近对话)1.4
crosschat send --conversation <cN|ref>回信——照抄信封里的短柄即路由1.4
crosschat status本机会话名单(裸跑三段式摘要;--conversations 看对话总览)1.4
crosschat doctor环境体检,❌ 才算失败环境搭建
crosschat install-skills把协议教材装进两个 agent 的技能目录环境搭建
crosschat claude / crosschat codex启动器:会话能收消息,旋钮透传,参数原样转发环境搭建、1.4
crosschat servetcp 星的起停与入网——跨机形态第3课
crosschat clean清理收件残留与 30 天未动的对话分片命令总表
crosschat uninstall卸载(连状态与技能一起清)命令总表

前六个是单机通信的日常面:装好后你反复用的就是 send 与 status;后三个一个是跨机、两个是运维清理。另有一个隐藏维护入口(__drain)不属于使用面——看门狗自己用的,1.5顺带见到它。

发送的三种形态

send 是唯一入口,日常就三种形态,各有各的时机:

形态什么时候用长什么样
--to 新消息开口找人或换话题crosschat send --to worker --body "……"
--conversation 回信收到信封后照抄回信crosschat send --conversation c21 --body "……"
管道长正文正文很长或含 $、引号等敏感字符'……' | crosschat send --to worker(省 --body)

三种形态在 1.4 第一次对话里各有一次真实实录;正文大小的上限与提额在 1.5 上限与限流。

1.4 第一次对话

对话全景

先说这一课最重要的事:你要记的不是命令,是“跟谁、说什么“。

crosschat 的第一视角是聊天——你用自己的话下令,agent 自己组装命令;命令是 agent 的语言,不是你的。整个过程长这样(下文三步全部真实走一遍):

👤 你(在自己的 agent 会话里说)  用 crosschat 向本机的 claude 会话「二期8」问好
🤖 你的 agent                    自己组装 crosschat send …(你不用记命令)
📨 二期8 收到信封                 信封里自带回信命令,照抄即回
🤖 你的 agent 收到回信            闭环——全程你没敲过一条 crosschat 命令

三步准备 + 一次对话:先把两个会话起起来(起两个命名会话),查一眼名单(查本机名单),然后一句话对话(发第一条消息)。想自己敲命令的形态收在后半——想跳着看也可以,节节都有真实实录。

起两个命名会话

命名是为了有地址可写。两侧的命名方式不对称,各自跟随原生习惯:

终端 1 起 claude 会话,名字是启动参数:

crosschat claude -n boss

终端 2 起 codex,名字在它的界面里改(随时可改,不用重启):

/rename worker

crosschat claude / crosschat codex 会原样转发其余参数(-c、resume、--model 照用),只多做了接收开通与身份清洗两件事(环境搭建立的习惯就是它)。名字可以带空格——命令里记得加引号:-n "boss 2"。

查本机名单

发之前看一眼对端在不在册——名单就是本机此刻全部可投递的会话:

> crosschat status
本机会话: claude 4 · codex 20
  claude 文档book  1 分钟前
  claude 单机通信专家  4 分钟前
  claude 二期8  17 分钟前
  claude ssh专家  42 分钟前
  codex beta12  10-07 23:04
(全量: --claude|--codex|--name)
tcp: 共 2 星
  …
(星内明细: --stars)
ssh: 无
(明细: --conversations --via ssh)

(tcp 星两行是第3课的地盘,没入网时显示 tcp: 无。)

隐式发生什么:裸跑给的是三段式摘要——本机会话计数加最近五个、tcp 星、ssh 对话,一眼看全貌。要挑人时加过滤:--claude / --codex 选段、--name <关键词> 按名字筛、--all 把没起名字的也带出来。名单是“活进程“名单:claude 注册表里进程已死的条目、codex 名册里已删的线程都不会出现——你看到的就是现在能收信的。这一步你也可以直接交给 agent(题词里写“先 status 确认名字“即可),它自己会查。

发第一条消息

现在到对话本身。👤 对自己的 agent 说(本课写作当天的真实实录——下令口径就是一句话):

用 crosschat 向本机的 claude 会话「二期8」问好

🤖 agent 收到题词,自己组装命令并运行——找谁变成 --to,说什么变成 --body:

> crosschat send --to 二期8 --body "你好二期8!第1课实录问好——收到请回一句即可。"
delivered to 二期8 (turn 3) · 续话 c19

回执一行三个信息点:delivered(投递三态之一)、turn 3(这对会话的第三轮——它们之前已经打过两次交道,--to 按名字自动接上了最近的对话,这就是“同名即续“)、续话 c19(这段对话的短柄,回信照抄它)。

回信随即到达——你没有做任何事,二期8 照信封里自带的命令回了信,你的 agent 被这条新消息唤醒:

<cross-session-message from-name="二期8" turn="4">
<crosschat-reply-hint reply-as="单机通信专家">回复请运行: crosschat send --conversation c19 --body "<你的回复>"(长正文/含敏感字符可省 --body 改管道喂 stdin)</crosschat-reply-hint>
收到,单机通信专家好!勘误那条已闭环(7e6db4e),1.7.0 待发。
</cross-session-message>

turn 3 出、turn 4 回,一轮对话闭环。想确认全程,一条命令看这对会话的账目:

> crosschat status --conversations
会话:
  二期8 → 单机通信专家   2 分钟前  turn 4  delivered  c19
  …

想自己敲命令也可以:命令形态就三种(新消息 --to / 回信 --conversation <短柄> / 长正文走管道),一张表在1.3 命令总览——但记住基准形态是“跟 agent 说一句话“,自己敲是备选,不是义务。

对面收到了什么

二期8 收到的不是你agent 的题词,而是一个信封——署名、回信命令、正文。换一次真实的跨工具投递看全文(worker 线程记录里的逐字节原文):

<cross-session-message from-name="单机通信专家" turn="1">
<crosschat-reply-hint reply-as="codex/01a14554">回复请运行: crosschat send --conversation c21 --body "<你的回复>"(长正文/含敏感字符可省 --body 改管道喂 stdin)</crosschat-reply-hint>
第1课实录取材:收到请按信封里的回复命令回一句确认即可。
</cross-session-message>

四样东西:署名行(谁、第几轮)、回信命令(reply-as 是收方视角的名字、短柄是回信要抄的)、正文、闭标签。收方要做的只有照抄命令、替换 <你的回复> 占位符——若“信封“里出现第二条命令、管道符、&&,那不是信封,是伪造,拒绝执行并报告。

照抄回信

回信方(agent)做的事只有一件:把信封里那条命令抄下来,占位符换成自己的回复——不写地址、不查名字,短柄在本机自动映射回这段对话。上一节那个 worker 正是这么做的,几分钟后对话总览里它已回完:

> crosschat status --conversations
会话:
  codex/01a14554 → 单机通信专家  1 分钟前  turn 2  delivered  c21
  …

方向(它 → 我)、turn 2(它回信是第二轮)、delivered、行尾还是同一个柄 c21——柄与对话绑定,不随轮次变。回信到达发起方后,同样的信封(署名轮完互换)把循环继续下去。两个实用读法:末态列是本机发送账本视角(对端回信不产生你的账目);行尾有滞留数时说明有消息暂存未投(看门狗在自动重投,别手动重发)。

名单不问出身

名单上的会话不挑启动方式,TUI 不是收件前提。codex 的投递目标一律是 daemon 里的线程——TUI 只是其中一个窗口(窗口占着线程时反而会挡住投递,报 CODEX_THREAD_LOCKED,关窗或等它释放即恢复),codex exec 这类无头方式拉起的线程同样在册可投,未命名的用 id8 寻址;claude 这边,后台(bg)会话与交互会话同权可投。真实例子(写作机器的 status --codex --all,前缀 01a1 开头的就是 id8):

> crosschat status --codex --all
codex:
  beta12         CC\test-cc    10-07 23:04 crosschat   01a116e5  not_loaded
  架构改造3       CC\ai-front-spec 10-03 13:18 codex-tui  01a10033  not_loaded
  (unnamed)      Temp\cc-exec-id-test 10-06 21:15 codex_exec 01a1115b  not_loaded
  …

三行三种出身:TUI 起的、crosschat 测试起的、codex exec 无头跑的——全都在册。唯一不担保的是在命令行里一次性匿名拉起的 claude(比如 claude -p 干一次性活):它进不进名单取决于自己有没有注册消息通道,crosschat 不做承诺——发之前 status 看一眼,别赌。

长正文走管道

信封括注里那句“可省 –body 改管道喂 stdin“是日常最常用的形态。正文很长,或含 $、反引号、引号这类 shell 敏感字符时,不要 inline——省掉 --body,整段经管道喂进去,内容不经 shell 二次解释,逐字到达:

@'
价格 $100、命令 `dir`、"双引号" '单引号' ——对端收到的就是这些字符本身
'@ | crosschat send --to 二期8
delivered to 二期8 (turn 1) · 续话 c19

(PowerShell 主线:单引号 here-string,收尾 '@ 必须顶格。Linux 一行带过:bash 用 cat <<'EOF' | crosschat send --to <名> 定界符带单引号,$ 与反引号同样逐字不展开。更多读文件正文、编码防坑的分层见 如何发送大内容。)

到这里,一轮完整对话走通了。每条命令背后踩着哪套地层——1.5 通信机制拆开看。

1.4 通信机制

1.4 第一次对话的每条命令背后踩着同一套地层。这一节把一条 send 拆成旅程看一遍,然后六件最值得知道的机制讲透:两条投递腿、身份、信封、回信认路、忙时三态、上限与限流。

一条 send 的旅程

crosschat send --to worker --body "……"
   │
   ① 入口补投      先扫一眼发件箱,有滞留顺手补投(不连坐本命令)
   ② 身份反查      从环境变量认出发送方(claude/codex/human)
   ③ 目标解析      名字全名 → 唯一前缀 → id8(1.3 的判定序)
   ④ 上限检查      正文 ≤ 配置额(默认 16KiB),超额就地报错教学
   ⑤ 轮次前进      对话引用 turn+1(--to 同名即续上最近对话)
   ⑥ 投递          ┌ claude 目标 → 注册表 → 命名管道一次写
   │               └ codex 目标  → proxy → daemon → turn/start
   ⑦ 记账          send-log.jsonl 落一行元数据;对话分片更新短柄
   │
   ▼ 跑完即退(无常驻)

七步全部住在一次进程生命周期里;跨步状态(分片、账本、发件箱)都在 ~/.crosschat/ 的小文件里,下一条命令接着读。逐步的文件级细节在 第四部 一条消息的完整路径。

claude 腿:注册表与管道

先看“不用它会怎样“:直接裸敲 claude 起的会话,管道上送来的消息会停在收件保持区——进程没开通跨会话入站,消息进不了对话。这就是 环境搭建立下“用 crosschat claude 启动“习惯的原因:启动器多注入一个设置(crossSessionInbound: accept),门才开。

开了门,这条腿的工作方式朴素到极致:

~/.claude/sessions/*.json          \\.\pipe\…(win)/ unix socket
┌────────────────────┐            ┌──────────────────────┐
│ 会话注册表(claude   │  挑出目标   │ 目标会话的管道         │
│ 自己写,crosschat    │ ─────────► │  win:auth 行 + 用户帧 │
│ 只读)              │  活进程过滤  │  unix:只发用户帧      │
│ kind=interactive/bg │            │ 写一次 → 关连接 → 退出  │
└────────────────────┘            └──────────────────────┘
  • 发现:扫 claude 自己的注册表,只认 interactive/bg 且进程活着的条目——死进程、守护进程类条目天然出局,所以名单永远是“现在能收信的“
  • 投递:往目标会话的管道写一帧用户消息,写完即走。win 上首行是 auth(token 从注册表旁的密钥文件读,文件名含 pid 与管道路径的哈希);unix 上省掉 auth——同一用户身份由内核凭据担保
  • 诚实:连接失败 = 确定没送到(CONNECT_FAILED);写了一半失败 = 状态不明(WRITE_UNCERTAIN),绝不自动重发——重发可能让对方收到两条,所以宁可让你核实

codex 腿:proxy 与 daemon

同样先看“不用它会怎样“:daemon 没跑,codex 侧整条腿瘫痪,报错长这样(真实实录,1.4起 worker 前捕获):

> crosschat status --codex
codex:
  unavailable (CODEX_PROXY_SPAWN_FAILED: Failed to establish the codex app-server proxy channel.)

报错尾部会附上它自己推荐的动作(codex app-server daemon start)。daemon 是 codex 生态的常驻件,crosschat 从不起停它——每次投递,crosschat 临时拉起一个 codex app-server proxy 短命进程接上去,用完整棵回收:

crosschat send(一次性)
   │ spawn(剥掉身份环境变量)
   ▼
proxy 进程 ── stdio 上做 WebSocket 升级 ──► app-server daemon(codex 的)
   │                                          │
   │  initialize → resume(线程) → 状态判定      │
   │  idle ──────► turn/start ──► inProgress ──┼──► 消息进入线程
   │  busy ──────► turn/start 照发 ────────────┤    (排队轮末处理 = queued)
   │  等审批 ────► 停:CODEX_APPROVAL_REQUIRED   │    审批只有用户能答
   │  TUI 占写者 ─► 等待 ~10s → parked(自动重投)│
   ▼ close:关 ws → 杀掉自己 spawn 的进程树      (daemon 毫发无伤)

两个设计点值得记住:每操作一条全新链,不池化不复用——状态不会串,崩了也是崩自己;忙 = 入队不等待(daemon ≥ 0.160 的开窗投递)——对方线程正跑着也照收,轮到它时处理,这就是三态里 queued 的来源。

身份怎么来的

发送方不需要自报家门——crosschat 从环境变量反查:claude 会话带着自己的管道变量(去注册表里恰好匹配到一个会话,就是它)、codex 带着自己的线程变量,两者都没有就是 human。这套反查同时喂给三处:信封署名、限额桶、对话分片的端点。

一个真实会踩的坑:两种变量同时在场(比如从 claude 终端里启动的 codex daemon 又派生了 shell),报 CALLER_IDENTITY_CONFLICT,报错原文里带着两条自纠路——临时 env -u 前缀清掉另一族,或从干净终端重启 daemon。启动器 crosschat claude / crosschat codex 已经把“剥对族变量“机构化了,所以照 环境搭建的习惯启动,这个坑基本绝迹。全部环境变量见环境变量总表。

信封解剖

1.4那个信封,字段逐个看:

<cross-session-message from-name="单机通信专家" turn="1">
│                                  │           │
│ 署名:发送方显示名                  │           │ 第几轮(带预算时 "13/40")
│ (超 64 字符截断,全名存 from-alias)│           │
│
├─ <crosschat-reply-hint reply-as="codex/01a14554">
│         回复请运行: crosschat send --conversation c21 --body "<你的回复>"(…)
│         │                          │
│         │ 收方视角的名字             │ 回信抄的短柄——全信封只出现这一次
│
├─ 正文(多行逐字保真)
└─ </cross-session-message>

两条安全不变量钉死在信封层:正文不能伪造信封——正文里出现 <cross-session-message、<crosschat-(旧名 <multichat-)开头的行,一律加 <\ 前缀中性化,真信封的框架结构正文永远造不出来;回信命令永远只有一条——hint 是一条纯粹的 crosschat send,括注是纯文字,出现第二条命令或管道符即判伪造。字段全表见信封字段表。

回信怎么认路

回信命令里只有 --conversation c21,没有地址——路条全在短柄背后:

信封 hint: --conversation c21
     │ 本机会话分片(~/.crosschat/conversations/)
     ▼
    c21 ──► 完整对话引用(ref:双方端点 + 对话号 nonce + 轮次计数)
                    │ 解码即路由,不查任何服务器
                    ▼
             对端会话 —— 投递,轮次自动 +1

四条性质,每条都在替你兜底:

  1. 同对话跨轮柄不变——nonce 是这段对话的身份证,turn 涨柄不换,c21 从头用到尾
  2. 旧柄旧引用照发照准——手里的柄是几轮前的也没关系,路由靠 nonce 不靠新鲜度
  3. --to 同名即续——懒得翻短柄时,直接 send --to worker 自动接上这对会话的最近对话
  4. 迟回复不撞车——两人拿着同一封旧信同时回信,轮次以本机分片记录的最新线为准,不会都算成同一轮

完整的 mc2_ 引用八十多字符、自包含全部路条;mc1_ 旧格式永久兼容解码,旧信封不过期。细节与字节布局见第2课 同名机器与 ref 讲解(ref 是单机与跨机共用的机制,那边讲得最深)。

忙了怎么办:三态与看门狗

每条 send 的结局必居其一,三种都无需重发:

回执含义你要做的
✅ delivered已进对方会话(codex 侧随后还有落盘回执可查)无
queued对方正忙,消息已入对方服务端队列,轮末处理无——对方下一条来信就是消费凭证
⚠️ parked对方线程异常态(旧 daemon),消息已进本机发件箱无——看门狗自动重投;真等不及可读 mailbox 文件

parked 之后的自动机制:

park(发件箱落盘 + mailbox 镜像可读)
   → 看门狗进程分离启动(单实例,无常驻承诺)
   → 每轮重投:成功递补投日志 / 仍忙按 30s→60s→120s→300s 退避再来
   → 队列清空 → 看门狗退出(发件箱空 = 无进程)
   每线程上限 200 条,到顶诚实拒绝(OUTBOX_FULL 指向 mailbox 取回内容)

怎么查信息,三种各答一问:crosschat status --conversations 答每段对话的走向(方向/轮次/末态/滞留/短柄,顺带把“暂未确认“的回执复查一遍);~/.crosschat/mailbox/<线程id>.md 答滞留消息的原文(人类可读镜像);~/.crosschat/send-log.jsonl 答审计——每条消息的结局一行元数据(谁、给谁、何时、什么状态),永不记正文,超 5MB 整份轮转成 .old。排查未送达的完整决策树见如何排查消息未送达,机制内幕见第四部 发件箱与看门狗。

上限与限流

正文超限长这样(真实实录,17KB 撞默认线):

> crosschat send --to 01a14554 --body "<17000 字符>"
crosschat: MESSAGE_TOO_LARGE: Body is 17000 bytes; the limit is 16384. 可提额:--max-body-kb <KiB> 或 CROSSCHAT_MAX_BODY_KIB=<KiB>(claude 端点硬顶 64KiB、codex 端点 1MiB,到顶只能落盘)。Write the content to a file and send the path instead.

一张表收全所有数字:

限制数值说明
正文默认上限16KiB三层来源:--max-body-kb > 环境变量 > 默认
配置绝对上界16MiB再大的配置也按此封顶
claude 端点硬顶64KiB配置再大也送不进这条腿——提额无效,只能落盘发路径
codex 端点硬顶1MiB同上
防乒乓限流30 条 / 60 秒 / 每对会话超了报 RATE_LIMITED,等一会儿或总结收尾——系统不静默丢弃

报错教学只指当前传输走得通的路:单机到硬顶就是“落盘,发路径“(--body "见 D:\report.md"——单机发文件路径就这一个形态);跨机的 scp / 文件通道是第2课与第3课的事。

最后把一个概念钉死:这些阈值全部是传输与状态治理,不是“上下文保护“。crosschat 不管理任何 agent 的上下文窗口,也管不了——16KiB 管的是一条消息走不走得进管道,30 条/60 秒管的是两个 AI 别无限对弹。对话真正的里程治理是轮次:turn 计数让你随时知道走了多远,--max-turn 预算(信封里 turn="13/40")在接近时提醒总结收尾——软提醒不硬拦,任务没完可以继续,但优先收尾。用法见如何控制对话轮次,16KiB 的由来见第四部。

边界:同机同用户与平台例外

三句话划清单机通信承诺的范围:

  1. 信任边界 = 同一台机器的同一个用户。unix 腿连 auth 都省了——内核担保管道两端同 uid;win 腿的密钥文件也只有同用户读得到。跨用户的隔离与防护,crosschat 不做也不承诺,见安全模型。
  2. win 的一个平台例外:codex daemon 的控制 socket 是 AF_UNIX,Windows 把它按登录会话隔离——从 sshd 会话、服务会话这类另一登录会话里投 codex 会吃连接拒绝(os error 10061)。日常终端与 daemon 同会话,无感;这条边界跨机侧同样成立,第2课 2.4有实测注记。
  3. 单机发文件没有专门通道:发路径用 --body,发内容走管道。文件级传输(--file)是 tcp 星的内建能力,第3课见。

一句话记住

单机通信 = 名字即地址、信封即路条:一条 send 读名册、算轮次、走管道,发完即退——对方自会带着短柄找回来。

第2课 跨机通信(ssh):两台电脑、没有中间服务器时消息怎么走

第1课里,消息从未离开过一台机器:名字在本机注册表里解析,投递走本机管道。这一课把范围扩大一圈——两台电脑,各跑着各的 agent,中间没有任何服务器。crosschat 的答案是复用 ssh:你在两台机器之间配一次免密登录(这件事运维世界已经做了三十年),消息就沿着这条旧路走,crosschat 自己零新增配置。

学完这课你能做到

  • 把两台机器配成 ssh 免密联邦,并用一条命令自证
  • 让本机 agent 给另一台机器上的 agent 发第一条跨机消息,读懂回执
  • 收到跨机消息后照抄回信,不写任何地址——回程自动选路
  • 正文超过大小上限时,用 scp 旁路把大内容送过去
  • 讲出一条跨机消息的五步链路,与每一步上隐式发生什么

前置

  • 读完第1课(信封、reply-hint、回执三态这些词不再解释)
  • 两台能互相 ssh 的机器;只有一台也不要紧,2.4 的一台机器的自环给了练习路径
  • Windows 机器需要管理员权限开一次 sshd;Linux 机器需要 root 或 sudo

2.1 背景与场景

一台机器一张名单

先说清楚:没有跨机通道的时候,人是怎么干活的。

第1课的 send(第1课 单机通信)之所以能按名字找到人,是因为名字登记在本机的会话注册表里——crosschat status 看到的就是这张名单。名单是本机的:A 机器上的 beta2 与 B 机器上的 beta2 互不相干,A 机器的 status 也永远看不见 B 机器的会话。

于是你就是邮差:在两个终端之间人工复制粘贴。A 机器上的 agent 说了一段话,你复制,切窗口,粘贴到 B 机器;对面回了话,再复制回来。消息走得通,但每一跳都是你的手,而且两个 agent 自始至终不知道对面是谁——名字、轮次、上下文,全都断在你这里。

消息出机器需要什么

crosschat 对跨机的答案克制到只剩一个参数:--via ssh:<对端>。

crosschat send --via ssh:yangwsl --to beta2 --body "……"

对 crosschat 来说,跨机 = 单机全部用法 + 一个 --via。命令到达对端后,以对端的完整本机权限执行——等价于有人在那台机器上敲了同一条 send:名字在目标机上解析,投递走对端自己的管道,忙时排队、异常暂存用的也是对端自己的机制。

crosschat 不新建任何连接层。网络、认证、加密全部复用 ssh;地址、端口、用户、密钥这些连接参数,住在每台机器自己的 ~/.ssh/config 里——这是运维世界三十年的存量标准。2.4 SSH 部署与配置要做的不是学新配置,而是把这份存量配好。

┌ crosschat 层(零自有配置)────────────────────────────┐
│  --via 解析 · origin 身份 · ref 对话引用 · 回执渲染    │
└────────────────────┬────────────────────────────────┘
                     │ 借道(不重造)
                     ▼
┌ ssh 层(连接的全部参数住这里)──────────────────────┐
│  ~/.ssh/config 别名 · 密钥 · known_hosts · 反向隧道  │
└─────────────────────────────────────────────────────┘
  2.4 配的每一样东西都在下面这层;上面这层一个配置文件都没有

打个比方:--via ssh:yangwsl 像是“把命令寄到 yangwsl 这台机器,让那里的 crosschat 干活“。类比到此为止——正文并不在命令行里,也不经过对端 shell 的解释(2.6 通信机制会讲为什么这条设计挡掉了一整类事故)。

双向可达与单向 NAT

ssh 形态有一个默认前提:两台机器彼此都能 ssh 到对方(同局域网、VPN、或双方都有公网地址)。按你的网络对号入座:

双向可达                              单向 NAT(本机在 NAT 后)
win ────► yangwsl                     win ────► 云端 Linux
win ◄──── yangwsl                     win ✗───── (云端连不回)
两台互装密钥,配完即用                回程由一条常驻反向隧道背回来
你的情况走 2.4 的哪块差量
两台互通(同 LAN/VPN)双向可达部署无,配完即用
本机能连出去、对端连不回你双向可达部署做完 + 单向 NAT 差量回程别名指向隧道口

单向不是残缺形态:出站方向(你→对端)与双向完全一样,只有回程改走隧道。隧道是 ssh 自己的能力——crosschat 代码里没有一行隧道逻辑,它只认 ~/.ssh/config 里的别名(单向 NAT 差量会把隧道伪装成一个普通别名,crosschat 毫无感知)。

2.2 术语

术语表

词一句话出现的地方
Host 别名~/.ssh/config 里给一台机器起的短名,crosschat 只认它当地址--via ssh:<别名>
hostname机器的系统名(hostname 命令输出),机器自述的身份别名的推荐取值、origin、ref
mid / mid8装机指纹的前 8 位(win 注册表 MachineGuid / Linux /etc/machine-id / mac IOPlatformUUID),同名机器靠它分辨回执机器段、ref 端点
origin随消息走到对端的发送方身份串(谁、哪台机器、指纹),由机器注入,agent 看不见跨机信封的署名来源
ref(mc2_)自包含的对话引用:双方端点 + 对话号 + 轮次,回信需要的全部路条信封 reply-hint、--conversation
短柄 cNref 的两字符本机缩写(c3 这样),照抄即路由信封回信命令、回执行尾
回执三态delivered(送达)/ queued(对方忙已入队,轮末处理)/ parked(旧版异常态,已暂存自动重投)每条 send 的输出
[via 主机]跨机报错前缀,标记错误发生在哪一跳;前缀后面是原样透传的自纠指引跨机失败输出

别名与 hostname

部署前最值得先讲清的一对关系:推荐把 ~/.ssh/config 里的别名直接写成对端的系统 hostname——对端敲 hostname 输出什么,你这边 Host 就写什么。

这是回信自动选路的软依赖,不是硬校验。手写 --via ssh:<任意合法别名> 永远可用;但跨机信封里的回信命令不带 --via(2.5 第一条跨机消息会亲眼看到),对端执行时按 ref 里的机器名自动补路由——补出来的正是发起方的 hostname。⚠️ 别名 ≠ hostname 时,“对端 ssh config 里有这个 hostname 当别名“就不成立,自动回程断掉(报 SSH_TRANSPORT_FAILED,指引补别名)。别名 = hostname,一次配对,终身免念。

机器指纹 mid

hostname 会撞名——真实世界就有两台机器都叫 yang 的联邦在跑。mid(machine-id)是每台装机唯一的指纹。crosschat 取它的前 8 位(mid8)放进回执机器段:

delivered to beta2@yangwsl(9f8e7d6c) (turn 1)
                        └── 这 8 个十六进制字符就是 yangwsl 的 mid8

两端 mid 都在时,“是不是同一台机器“由 mid 说了算——hostname 相同也骗不过去;任一端 mid 读不到,才退回比 hostname(2.6 同名机器一节看它怎么工作)。

2.3 命令总览

一张地图收拢本课的全部命令——每条一句话,点链接直达详解。还没部署的先去2.4 SSH 部署与配置,配完回来。

命令一句话详解
crosschat send --via ssh:<别名> --to <名>发起跨机消息,名字在对端机上解析发起跨机消息
crosschat send --conversation cN --body "…"回信照抄信封命令,不写地址,回程自动选路回信不写地址
ssh <对端> crosschat status看对端的会话名单——别赌名字发送查对端名单
ssh <对端> crosschat doctor远程望诊对端环境(能跑吗)体检与望诊
crosschat status --conversations --via ssh本机看 ssh 腿的跨机对话台账体检与望诊
scp <文件> <对端>:/tmp/ + 发路径正文超限时的旁路三步正文超限时

另两条部署期命令住在 2.4:探活 ssh peer 与出腿自证 ssh -o BatchMode=yes peer crosschat --version。全课命令就这些——没有更多了,记住“跨机 = 单机 + 一个 --via“就够了。

2.4 SSH 部署与配置

这一节做完,你得到:两台机器互相认得对方的 ssh 密钥、彼此免密登录、crosschat 出腿自证通过。本质就是配一次双向免密 ssh——crosschat 复用它,自己零额外配置。

演示主线是 Windows 本机 ↔ Linux 对端(WSL 与远程 Linux 同理);Linux 侧的每步在对应位置一行带过。双向场景按下面八步走;单向 NAT 先把八步做完,再补 单向 NAT 差量。

双向可达部署

第一步 · 查本机 hostname 与用户(登记下来——对端写它的 ssh config 时要用这两个值)

> hostname
win
> whoami
yangsheng

(对端同样查:在 Linux 上敲 hostname。两台机各自的 hostname 就是两边别名的取值来源)

第二步 · 开本机 sshd(收件腿的终点——对端要能 ssh 进来给你投递)

> Get-Service sshd
Status  Name
Running sshd
# Stopped → 管理员 PowerShell 跑 Start-Service sshd
# 服务不存在 → 管理员 PowerShell 跑 Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 然后 Start-Service sshd

(Linux 对端:systemctl enable --now ssh) win 收件腿的一个实测定论:codex 不可达(AF_UNIX 跨登录会话隔离),claude 腿可用——要给 win 发消息,选 claude 会话。

第三步 · 本机密钥(有则跳过;生成时一路回车,无口令——这是机器通道)

> Test-Path $env:USERPROFILE\.ssh\id_ed25519.pub
True
# False → ssh-keygen -t ed25519 一路回车

(Linux:ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519)

第四步 · 公钥交换——两台机器互装对方的公钥,⚠️ 漏任何一侧必卡认证

第一步 打印            第二步 装入                        效果
win   id_ed25519.pub ─────► Linux ~/.ssh/authorized_keys      (win 免密连 Linux ✓)
Linux id_ed25519.pub ─────► win   administrators_authorized_keys(Linux 免密连 win ✓)
两支方向都要装;漏一支 = 一个方向通、另一个方向卡认证

win 的公钥给对端:打印 Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub,把整行追加到对端的 authorized_keys:

echo '<win公钥整行>' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
# 对端恰好是本机 WSL 时可不走 ssh 直写(鸡生蛋解法):
#   wsl -u root sh -c 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' < $env:USERPROFILE\.ssh\id_ed25519.pub

对端的公钥给 win:管理员组用户必须走专用文件,三条在**终端(管理员)**里跑,无输出 = 成功:

> $kf = "$env:ProgramData\ssh\administrators_authorized_keys"
> Add-Content $kf -Value '<对端公钥整行>'
> icacls $kf /inheritance:r /grant "SYSTEM:(F)" /grant "BUILTIN\Administrators:(F)"

第五步 · 认得对端(写 ssh config;⚠️ 别名必须 = 对端 hostname,原因见别名与 hostname)

> notepad $env:USERPROFILE\.ssh\config

文件末尾追加(占位符换成真值;peer 是顺手起的短名,主别名用对端系统 hostname):

Host <对端hostname> peer
  HostName <对端地址>
  Port <对端端口>
  User <对端用户>

WSL 注记:镜像网络下 22 端口被 win 占——对端先换非标端口 sed -i 's/^#*Port .*/Port 2222/' /etc/ssh/sshd_config && systemctl enable --now ssh,然后本步的 Port 填 2222。 可选提速:同一 Host 块下加 ControlMaster auto + ControlPath ~/.ssh/cm-%r@%h-%p + ControlPersist 10m,复用连接,摊薄每次握手 100-300ms。

第六步 · 播种对端指纹(首连自动记指纹,防后续卡密钥确认)

> ssh -o StrictHostKeyChecking=accept-new -p <对端端口> <对端用户>@<对端地址> exit
Warning: Permanently added '<对端地址>' (ED25519) to the list of known hosts.
# 公钥交换已做完 → 密钥直接认证,无提示直接退出 = 正常
# 还没做完 → 提示输一次对端密码,输完即退,也算通过

别用 ssh-keyscan 播种:它有算法协商缺陷,输出永远只有 # 开头的横幅行,抓不到指纹。

第七步 · 探活(输密码能进对端 shell = 网络与账号都通;进去敲 exit 退回)

> ssh peer

第八步 · 出腿自证(部署完成的判据)

> ssh -o BatchMode=yes peer crosschat --version
1.6.4

这一条同时验了四件事:网络通、密钥对、对端找得到 crosschat、版本不过旧。对端反方向再跑一条(ssh <本机hostname> crosschat --version),✅ 两边都出版本号 = 部署完成。

装完顺手再补两条望诊(详解见体检与望诊):

> ssh peer crosschat doctor
> ssh peer crosschat status

第一条看对端环境能跑(Node / codex daemon / skill),第二条看对端有人能收(会话名单)。

对端是 Linux 时,它的同侧步骤每步一行(还没装 crosschat 先装:Node ≥22,npm i -g @oatelauser/crosschat && crosschat install-skills):

hostname                                             # 查 hostname(写进 win 的 config)
systemctl enable --now ssh                           # 开 sshd
ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519    # 密钥
echo '<win公钥整行>' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys   # 收 win 公钥
# ~/.ssh/config 追加:Host <win的hostname> peer 三行(HostName/Port/User)——别名 = win 系统 hostname
ssh -o StrictHostKeyChecking=accept-new <win地址> exit    # 播种 win 指纹
ssh peer                                             # 探活
ssh -o BatchMode=yes <win的hostname> crosschat --version # 反向自证

单向 NAT 差量

前提:上面的双向八步已做完。你的机器在 NAT 后能连出去、云端连不回你——回程由本机一条常驻反向隧道背过去:

  • win 用 Xshell:会话属性 → 连接 → SSH → 隧道 → 添加“远程(传入)“,源 localhost:2222,目标 localhost:22,保持活动间隔 30s,保持会话开着
  • 或 Terminal 跑一条挂着:ssh -N -R 2222:localhost:22 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes peer
出站腿(与双向场景完全一样)
  win ─────────────── ssh ──────────────► 云端 Linux

回程腿(云端连不回 NAT 内的 win,改走常驻反向隧道)
  云端 Linux
    │  对端别名 win = localhost:2222
    ▼
  localhost:2222 ══ 隧道(随 Xshell / ssh -R 会话常驻)══► win 的 sshd:22

验证隧道口在听(贴到云端会话里跑):

> ss -tln | grep 2222
LISTEN 0 128 127.0.0.1:2222 0.0.0.0:*

然后在对端把回程别名写成指向隧道口(双向场景第五步的对端侧在此换成这份):

Host win
  HostName localhost
  Port 2222
  User <win用户名>

⚠️ 三条纪律,每条都是真机实测翻出来的:

  1. User 行必写——缺了它,ssh 默认拿对端自己的用户名去连你 → root@localhost: Permission denied(2026-10-08 实测翻车点)
  2. Port 必须是 2222——写成 22 等于连到对端自己的 sshd → Host key verification failed
  3. 隧道随会话活——会话断了回信就断(出腿不受影响),重连即恢复

一条命令同时完成播种与回程自证(在云端跑;BatchMode = 只走密钥不问密码):

> ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes win echo ok
Warning: Permanently added '[localhost]:2222' (ED25519) to the list of known hosts.
ok
# 报 Permission denied = win 侧公钥交换还没做完——指纹已存好,做完重跑本条即出 ok

✅ 再补一条全环自证(本机上跑,本机→云端→隧道→本机 整圈):

> ssh -o BatchMode=yes peer "ssh -o BatchMode=yes win crosschat --version"
1.6.4

操作细节全流程见指南部如何使用反向隧道。

远端找得到 crosschat 吗

sshd 起的登录 shell 只见系统 PATH。对端先跑判别(Linux 上):

> npm prefix -g
/usr
# 输出以 /usr 开头(发行版/NodeSource 装的 Node)→ 没有坑,本节跳过
# 输出是 ~/.nvm/... 或 /opt/...(nvm/自定义前缀)→ ⚠️ 必须软链:
> ln -s "$(npm prefix -g)/bin/crosschat" /usr/local/bin/crosschat

不软链的后果——出腿自证会这样报:

crosschat: REMOTE_FAILED [via peer] 远端异常退出(127): command not found

Windows 对端的等价解法:把 npm 全局目录加进系统 PATH。上一步的自证命令,验的正是这件事。

一台机器的自环

没有第二台机器,也能把本课全部跑一遍——消息真的走一轮 ssh 管道再回到本机会话(sshd 已在 22 端口):

> crosschat send --via ssh:localhost --to <本机会话名> --body "自环测试"
delivered to <会话名>@win(3f2a81b0) (turn 1) · 续话 c1

2.6 通信机制的全部机制在这条命令里照常运转。

2.5 第一条跨机消息

对话全景

你发跨机消息的方式跟单机一样:在自己的会话里说一句话——组装命令是 agent 的事,不是你的事。这一节用三步走完第一次跨机对话,全程你不需要记任何命令。

第一步 · 两头会话就位

你这一头:你本来就在一个会话里(比如 crosschat claude -n boss 起的那个)——你在里面说话,它替你发消息。对端那一头:yangwsl 机器上有个命名会话——让对端在自己的会话里 /rename beta2,再随便发一条激活即可(机器怎么配上 ssh 的,2.4 SSH 部署与配置已经做完,这里不再重讲)。

第二步 · 查对方会话

跨机的名单在对端机器上,本机 status 看不见——发之前看一眼,别赌名字:

> ssh yangwsl crosschat status
本机会话: claude 1 · codex 1
  claude beta2  3 分钟前
  codex workteam  12 分钟前
(全量: --claude|--codex|--name)
tcp: 无
ssh: 无
(明细: --conversations --via ssh)

第三步 · 一句话下令

👤 你在 boss 的会话里说:

用 crosschat 走 ssh 跨机给 yangwsl 机器的会话「beta2」问好,要求对方回复。

🤖 boss 自己组装命令、自己发(那条命令长什么样,在下一节——那是 agent 的语言,你不用记)。然后整段对话自己走完(真实实测实录,📨 是 agent 收到的外来消息):

🤖 beta2 收到             📨 来自另一会话的消息:
                          <cross-session-message from-name="claude/boss" turn="1" from-machine="win">
                          <crosschat-reply-hint reply-as="beta2">回复请运行: crosschat send --conversation c3 --body "<你的回复>"(长正文/含敏感字符可省 --body 改管道喂 stdin)</crosschat-reply-hint>
                          你好 beta2,这是跨机问候
                          </cross-session-message>
🤖 beta2                  (照 hint 抄了一条回信命令——一个地址都没写)
🤖 boss 收到              📨 收到!跨机闭环成立(turn 2,自动经 ssh 回到 win)
win · boss                              yangwsl · beta2
   │                                       │
   │ ① send --via ssh:yangwsl --to beta2   │
   │ ────────────── ssh 管道 ─────────────► │ ② 收到信封(from-machine="win",
   │                                       │      回信命令不带 --via)
   │                                       │
   │                                       │ ③ 照抄 hint 回信 --conversation c3
   │ ◄────── 回程按 ref 自动选路走 ssh ───── │
   │ ④ 收到回复(turn 2)                    │

与单机的全部差别只有四个字:题词里多了「走 ssh 跨机」。命令由 boss 组装,beta2 照信封回信,双方谁都没敲过一次完整地址。

下面几节把 agent 用的(或你想直接敲的)命令形态逐一拆开——每条都是:用途 → 完整实录 → 隐式发生什么。

发起跨机消息

全景第三步里,boss 替你组装的就是这条;你想自己直接敲,形态也一样:

> crosschat send --via ssh:yangwsl --to beta2 --body "你好 beta2,这是跨机问候,收到请回"
delivered to beta2@yangwsl(9f8e7d6c) (turn 1) · 续话 c1

隐式发生什么,一段讲完:这条命令没有在本机投递——crosschat 组装了一条固定的远端命令,经 ssh 送到 yangwsl 执行,等价于有人在那台机器上敲了 send --to beta2。名字 beta2 在对端名单里解析,本机同名会话与此无关。回执从对端传回后按本机格式重渲染,所以输出跟单机几乎一样,只有收件人多了机器段 @yangwsl(9f8e7d6c)——名字加对端指纹(2.2 机器指纹 mid)。忙时三态照单机语义:

queued to beta2@yangwsl(9f8e7d6c) (turn 2; 对方正忙,已入队,本轮结束即处理) · 续话 c1

已寄存给 beta2@yangwsl(9f8e7d6c)(对方忙,未送达)。队列共 1 条,看门狗每 0.5–5 分钟自动重试,无需手动 status。
人工随时可读滞留内容: ~/.crosschat/mailbox/<线程ID>.md(位于 yangwsl)
续话: c1

delivered / queued 都无需重发;parked 是旧版守护的异常态,已暂存、看门狗自动重投,同样无需手动干预。行尾「续话 c1」是本机配的会话短柄,续话照抄。

❌ 再把别名敲错一次,看看跨机报错长什么样(传输层不通时):

> crosschat send --via ssh:yangsl --to beta2 --body "…"        # 别名手滑少了个 w
crosschat: SSH_TRANSPORT_FAILED: ssh 无法到达 yangsl(不通/密钥/known_hosts)。自证: ssh yangsl crosschat --version(顺带核对版本);检查 ~/.ssh/config 与网络。

错误自带头诊断命令——照抄跑一遍就知道断在哪一层。

还有一条纪律在此立住:跨机轮次纪律。防乒乓限流器是各机各自记账的,跨机没有全局限流——turn 轮次是唯一防线。接近预算就总结收尾,别无限往返。

回信不写地址

对端的 beta2 要回复时,它也只是照抄信封里的命令——一个地址都不用写,你的视角里依然是零命令:

> crosschat send --conversation c3 --body "收到!跨机闭环成立"
delivered to claude/boss@win(3f2a81b0) (turn 2) · 续话 c3

隐式发生什么:回信命令里没有 --via,也不需要——发起方机器名已经坐在 c3 背后的对话引用(ref)里,执行侧按它自动补路由:本机 ssh config 里有 win 这个别名,就走 ssh;没有别名但本机入了 tcp 星(第3课),就换 tcp 腿;都没有就照旧试 ssh 并在失败时报错指路。这就是 2.2 别名与 hostname 那句“别名 = hostname 是软依赖“的落点:win 在 yangwsl 侧的别名恰好叫 win,回程自动成立。

信封括注里那句“长正文/含敏感字符可省 –body 改管道喂 stdin“对跨机同样成立——正文走 stdin 管道,不进任何一层的命令行,$、反引号、引号逐字到达(第1课 单机通信的管道手法原样可用,这也是 2.6 通信机制会展开的一条设计)。

查对端名单

全景第二步那条命令的门道与名单的细节:

这就是一次普通的 ssh 远程命令,跑的是对端自己的 status——你看到的是对端那张名单,过滤参数照常可用(ssh yangwsl crosschat status --claude)。

名单上的会话也不问出身,TUI 不是收件前提。codex 的投递目标一律是 daemon 里的线程——TUI 只是其中一个窗口(窗口占着线程时反而会挡住投递,报 CODEX_THREAD_LOCKED,关窗或等它释放即恢复),codex exec 这类无头方式拉起的线程同样在名单里,未命名的用 id8 寻址;claude 这边,后台(bg)会话与交互会话同权可投。例外只有 2.4 说过的 win 收件腿:codex 不可达(AF_UNIX 跨登录会话隔离)。唯一不担保的是在命令行里一次性匿名拉起的 claude(比如 claude -p 干一次性活)——它进不进名单取决于自己有没有注册消息通道,crosschat 不做承诺:发之前 status 看一眼,别赌。

反方向看自己:跨机对话发起后,本机的 crosschat status --conversations 会出现这一对(发起侧也记账):

会话:
  文档book → ssh专家    9 分钟前   turn 6   delivered  c17

一行里双方、最近轮次、投递状态、行尾续话柄都在。注意 status --via tcp:<机器> 那种远程枚举形态是第3课 跨机通信(tcp)的 tcp 通道能力;ssh 对端就用上面这条 ssh 命令。

体检与望诊

三条命令回答三个问题:本机环境能跑吗(doctor)、对端环境能跑吗(远程 doctor)、我的跨机对话都怎么样(status 过滤)。

先立一个边界:doctor 不检查 ssh 联邦。它体检的是所在机器自己的环境——Node 版本(≥22)、claude 可路由会话、codex CLI 与 daemon 版本(≥0.160 才有忙时入队)、双侧 skill 安装、双身份残留;入了 tcp 星的机器另查星条目与 broker 可达。ssh 别名、密钥、连通性这些联邦项不在它的清单里——联邦诊断走 2.4 的自证命令。

> crosschat doctor
环境
  ✅ Node v22.9.0 (≥22)
  ✅ claude CLI C:\...\claude.exe;可路由会话 2 个
  ✅ codex CLI 0.160.0
  ✅ codex daemon 0.160.0
  ✅ claude 侧 skill 与安装源一致
  ✅ codex 侧 skill 与安装源一致
  ✅ 无双身份残留
结论: 7 项通过,0 项警告,0 项错误

读法两条:只有 ❌ 让退出码非 0(脚本可判);⚠️ 是合法状态的教学提示——codex 没装、daemon 没跑都只警告不判错,“这台机器是纯 claude 机“是合法答案。对端没入 tcp 星时 tcp 段整段不出现,就像上面临录这样干净。

远程望诊就是同一条命令经 ssh 到对端跑——你坐在本机看对端的体检报告:

> ssh yangwsl crosschat doctor
环境
  ✅ Node v22.11.0 (≥22)
  ✅ claude CLI /usr/local/bin/claude;可路由会话 1 个
  ⚠️ codex daemon 未运行;要用 codex 通道时先执行: codex app-server daemon start
结论: 5 项通过,1 项警告,0 项错误

(实录里的 ✅⚠️ 是 doctor 程序自己打的,不是排版装饰——照抄即得同样输出。)最后是 status 的 ssh 专属过滤:

> crosschat status --conversations --via ssh
会话:
  文档book → ssh专家    9 分钟前   turn 6   delivered  c17

--via ssh 只留 ssh 腿的对话段(tcp 的用 --via tcp)——跨机对话多了以后,这一条就是你的 ssh 联邦台账。

正文超限时

用途:正文超过大小上限时,按报错指的路走 scp 旁路——大内容不过消息通道。

❌ 先看本地直接挡下的样子(默认上限下硬塞 20KB):

> crosschat send --via ssh:yangwsl --to beta2 --body "<20KB 长文>"
crosschat: MESSAGE_TOO_LARGE: Body is 20000 bytes; the limit is 16384. 可提额:--max-body-kb <KiB> 或 CROSSCHAT_MAX_BODY_KIB=<KiB>(claude 端点硬顶 64KiB、codex 端点 1MiB,到顶只能落盘)。跨机大内容(ssh 形态):先 scp <文件> yangwsl:/tmp/<文件名>,再 crosschat send --via ssh:yangwsl … --body "见 /tmp/<文件名>"(scp 与 --via 共用同一份 ssh 配置)

❌ 还有一种更隐蔽的:本机提了额、出发了、在对端被拦下——错误带 [via] 前缀回来,教学也换成接收方视角:

> crosschat send --via ssh:yangwsl --to beta2 --body "<20KB 长文>" --max-body-kb 64
crosschat: MESSAGE_TOO_LARGE: [via yangwsl] Body is 20000 bytes; the limit is 16384 on this machine (a --via remote send). 发送方本机提额无效——上限属接收方(本机)配置。大内容走 scp 旁路:发送方先落盘,scp <文件> <接收方>:/tmp/<文件名>,再 crosschat send --via ssh:<接收方> … --body "见 /tmp/<文件名>"(scp 与 --via 共用同一份 ssh 配置)。确需直发大内容:由接收方(本机)操作者调 CROSSCHAT_MAX_BODY_KIB。

✅ 照报错教的做(scp 与 --via 共用同一份别名和密钥,2.4 SSH 部署与配置配一次全通):

> scp big-analysis.txt yangwsl:/tmp/big-analysis.txt
> crosschat send --via ssh:yangwsl --to beta2 --body "分析 /tmp/big-analysis.txt,结论报过来"
delivered to beta2@yangwsl(9f8e7d6c) (turn 3) · 续话 c1
① 本机落盘 big-analysis.txt ──► ② scp 到对端 /tmp/ ──► ③ send 只发一句话(带对端本地路径)
   (超限的大正文留在这里)        与 --via 同一份别名/密钥      消息通道只走路径引用

大结果回传同理反向走一遍(对端 scp 回来,或题词让对端把结果落盘发路径)。

隐式发生什么,把这套数字一段讲完:默认上限 16KiB;--max-body-kb 或环境变量可调高,但 claude 投递管道硬顶 64KiB、codex 硬顶 1MiB,到顶提额无效;ssh 跨机腿在出发前按最宽的 1MiB 预检——超线本地直接挡下,ssh 根本不会发起;过了预检的,到对端还要用对端自己的限额复检一次(上面第二条实录就是)。所以超限的正解从来不是硬塞,而是 scp。要说清楚的是:scp 不是 crosschat 的内建通道——代码负责预检、并在报错里教这三步,真正执行 scp 的是你(或 agent)。win↔WSL 这对还有更省的捷径:文件根本不用过网络,WSL 直写 win 盘(cp result.txt /mnt/c/Users/<你>/AppData/Local/Temp/),用完即删。

2.6 通信机制

2.5 第一条跨机消息的每条命令背后都踩着同一套地层。这一节把一条消息拆开看五步,然后把四个最值得知道的机制讲透。

一条消息的五步

本机(win)                                 对端(yangwsl)
──────────────────────────                  ──────────────────────────
① 组装固定命令
   ssh yangwsl -o BatchMode=yes
       -o ConnectTimeout=10
       crosschat send --to beta2
       --json --origin <身份串>
② 正文写进 stdin ──── ssh 加密管道 ────────►  ③ 对端登录 shell 执行 crosschat
   (命令行里没有正文)                        = 有人在这台机器上敲了
                                              同一条 send:解析名字、
                                              投递、排队/暂存全在对端
⑤ 本机重渲染回执 ◄──── stdout(JSON)────────  ④ 回执 JSON 写回 stdout
   delivered to beta2@yangwsl(9f8e7d6c)…
   (回程路条已经坐在 ref 里)

①② 在本机、③④ 在对端、⑤ 回到本机。五步里没有第三台机器、没有中心服务——这就是课题里“没有中间服务器“的意思。

再换组件视角看同一件事——每台机器上各自跑着什么:

本机 win                                      对端 yangwsl
──────────────────────────                    ──────────────────────────
crosschat send(上面的①②步)                   登录 shell
 · 组装固定命令、注入身份、正文进 stdin ──ssh──►  └─ crosschat send(完整本机权限)
 · 会话分片:短柄 cN、续话线                       · 会话名单:beta2 在这里解析
 · send-log:发起侧记账                ◄─stdout── · 投递管道:忙时排队、暂存+看门狗
 · 重渲染回执(名@name(mid8))                    · 会话分片:信封里 c3 那个柄住这里

对端跑的就是一条完整的本机 send——没有任何“功能受限的远端模式“;跨机与单机共享同一套代码路径,这是“跨机 = 单机 + 一个 –via“在架构上的 literal 含义。

远端命令面为什么固定

① 里那条远端命令是写死的字面:目标(--to 或 --conversation)、--json、--origin,仅此几样,一个可配置项都没有。正文永远走 stdin,永远不出现在命令行里。

这不是偷懒,是安全设计。远端命令经对端的登录 shell(cmd / PowerShell / bash,三家引号规则互相打架)解释——任何“可配置的远端命令“都是一个注入面。项目的选择是让危险字符根本进不了命令行,而不是进了再转义:host 别名只允许字母、数字、.、_、-;--to 的值禁空格与 shell 元字符(名字带空格是单机语义,跨机改用 id8 / 会话引用寻址);--conversation 的值必须 mc1_/mc2_ 开头。三道白名单守的都是同一条防线,正文则整个绕开战场走 stdin——所以 2.5 第一条跨机消息里 $、反引号、引号才敢说“逐字到达“。

三层错误两个超时

失败分三层,每层有自己的错误码和自救动作:

层错误码含义与自救
本地等超时(或 ssh 进程没退)SSH_TRANSPORT_TIMEOUT⚠️ 等满上限杀了本地进程,远端状态不明——勿盲目重发(可能与首次投递叠加),先在对端查 send-log 或 status 核实
ssh 自身失败(退出码 255)SSH_TRANSPORT_FAILED不通/密钥/known_hosts;自证命令就在报错里:ssh <对端> crosschat --version
远端 crosschat 业务错原错误码 + [via 对端] 前缀对端把 crosschat: CODE: message 写到 stderr,本机从 stderr 匹配后同码重抛——MESSAGE_TOO_LARGE 的自纠指引跨机原样有效;匹配不上的(对端崩了)归 REMOTE_FAILED,带 stderr 尾部

两个超时叠着用:外层是本机等待上限,默认 120 秒,环境变量 CROSSCHAT_SSH_TIMEOUT_MS 可调;内层是命令行里固定的 ConnectTimeout=10,TCP 连不上 10 秒就断,不陪它等满 120。BatchMode=yes 同理——任何交互提示(问密码、问指纹确认)都变成快速失败,而不是挂死到超时。

身份跟着消息走

对端的 sshd shell 里没有 agent 的环境变量——没有下面的机制,你的 claude 会话跨机发消息,到对端会被认成“human“,对话从此没法回。

解法是 --origin:本机发送时把调用方身份(claude/codex/human、完整会话 id、显示名)连同本机 hostname 和 mid 打成一个串,整体 base64 编码,作为命令行的一枚旗标带给对端。它是机器注入的,不出现在帮助里,agent 平时看不见——但对端就靠它署名:信封里的 from-name 与 from-machine 都来源于此。

两条不变量,都从真实缺陷里固化而来:

  1. origin 在场即权威。对端进程若泡在残留的 agent 环境变量里,也不能让残留改写署名——否则对方来信会被标成本机会话发的(真实缺陷 B3.1,已回归锚定)。
  2. 完整 id 才可路由,显示名只给人看。早期版本把显示名放进了路由字段,回信路条整个断掉(2026-10-07 实测缺陷,现有回归测试锚定)。

回信怎么认路

2.5 第一条跨机消息的回信命令只有 --conversation c3,没有地址——因为回信需要的全部路条,压缩在 ref 这个自包含的串里:双方端点(工具 + 完整 id + 机器名 + 指纹)、一个对话号、轮次计数。它不查询任何服务器,解码即路由。

ref 以 mc2_ 开头,后面是一段 base64url 的二进制打包。三个设计要点:

mc2_<base64url>
┌─────────┬─────────────┬─────────────┬────────┬────────┬───────┬──────┐
│ flags   │ 发起方 id   │ 收件方 id   │ 机器名 │ 指纹   │ 对话号│ 轮次 │
│ varint  │ UUID=16裸字节│ UUID=16裸字节│ 长度+串│ 长度+串│ 裸字节│varint│
└─────────┴─────────────┴─────────────┴────────┴────────┴───────┴──────┘
  类型码与存在位     │← 非 UUID 的 id 走"长度前缀+原串"逃生门,格式漂移不翻车
  1. flags 一枚 varint 记全部布局——两端是什么类型、每个可选字段在不在,一位一义
  2. UUID 压 16 个裸字节——claude/codex 的会话 id 恰是 UUID 形态,最高频路径最省;不是 UUID 的走长度前缀 + 原串,id 格式将来漂移也不会翻车
  3. 轮次用 varint——不是定长一字节,长对话过了 127 轮照样编

这套打包把回信路条从约 290 字符压到约 84——信封每轮都要带它,双方上下文都在付这个成本。日常你更不会见到完整 ref:信封和回执行尾给的是两字符短柄(c3),柄在本机会话分片里映射回完整 ref,照抄即路由;柄的分配与消息成败解耦(发失败了也不污染续话线)。旧的 mc1_ 格式(一版 JSON 打包)永久兼容解码,旧信封不过期。

回程选路在执行侧自动做,决策就三问:

对端执行「--conversation 回信、无 --via」
    │ 从 ref 解出对端机器名(= 发起方 hostname)
    ▼
本机 ssh config 里有这个别名吗?
    ├─ 有 ──► 走 ssh(2.5 走的就是这条)
    └─ 没有 ── 本机入了 tcp 星吗?
              ├─ 有 ──► 换 tcp 腿(第3课)
              └─ 没有 ─► 照旧试 ssh,失败时报错指路

你手写的 --via 永远一字不动,自动选路只在它缺席时补位。(tcp 会话的 ref 还多一段星标识,那是第3课 跨机通信(tcp)的地盘。)

同名机器

两台机器都叫 yang 的联邦真实存在。这时 hostname 失效,mid 顶上:

  • 两端 mid 都在 → mid 相同才算同一台机器;hostname 相同骗不过去
  • 任一端 mid 读不到 → 退回比 hostname(老语义,mid 不可读时仍正确)

跨机来件发现“对端 hostname 与本机相同、指纹不同“时,投递照常跨机路由,但回执会追加一行人话警告:

⚠ 两台机器同名 (yang):已按跨机路由(machine-id 不同)。建议改名避免混淆。

顺带一个实现细节:win 的 MachineGuid 带连字符、Linux 的 machine-id 是纯十六进制——比对前统一归一(小写、去非十六进制字符),但只在比对时归一,存储与传输保持各自原样。

一句话记住

跨机 = 单机 + 一个 --via:命令走一条固定的 ssh 管道,身份和回程路条都在消息自己身上——所以你只管发,地址的事系统自己认得。

第3课 跨机通信(tcp)

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

3.1 背景与场景

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

3.2 术语

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

3.3 命令一览

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

3.4 起星与入网

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

3.5 会话入星与寻址

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

3.6 跨机消息与文件

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

3.7 升级

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

如何查看在线会话

一条命令三张视图

用途:crosschat status 一条命令,按你要回答的问题切三张视图——谁在线(裸跑摘要)、挑人(过滤旗标)、这段对话走到哪了(--conversations)。

> crosschat status
本机会话: claude 4 · codex 20
  claude 文档book  1 分钟前
  claude 单机通信专家  4 分钟前
  claude 二期8  17 分钟前
  claude ssh专家  42 分钟前
  codex beta12  10-07 23:04
(全量: --claude|--codex|--name)
tcp: 共 2 星
  …
ssh: 无
(明细: --conversations --via ssh)

隐式发生什么:入口处先顺手补投一遍发件箱滞留消息(有缓解会打一行 stderr,没有则无声),然后读两张登记册——claude 注册表与 codex 线程名册,各过滤掉死进程与已删线程。所以名单永远是此刻能收信的,不是“曾经开过的“。

裸跑摘要怎么读

三段各答一问:

  • 本机会话——计数一行(claude N · codex N,滞留消息多时附 滞留 N(看门狗运行中/未运行)),下面是最近五个命名会话(跨两侧按最近活跃混排)。未命名的默认不占这五行。
  • tcp——入网的星与机器(没入网显示 tcp: 无;这是第3课的地盘,单机阶段看一眼即可)。
  • ssh——跨机对话段数(ssh: 无 就是还没有)。

一行结论:看“现在有谁、都在不在“,裸跑就够了。尾巴上两行括注就是切换提示——全量用过滤旗标,对话明细用 --conversations。

挑人与选段

给 --to 找收件人时的两张滤网:

旗标作用例
--claude / --codex只看一侧crosschat status --codex
--name <关键词>按名字大小写不敏感筛crosschat status --name 二期
--all把未命名会话也带出来(默认隐藏)crosschat status --codex --all

未命名的 codex 线程长这样(--all 才可见,行首 id8 就是投递地址):

> crosschat status --codex --all
codex:
  beta12         CC\test-cc    10-07 23:04 crosschat   01a116e5  not_loaded
  (unnamed)      Temp\cc-exec-id-test 10-06 21:15 codex_exec 01a1115b  not_loaded
  …

codex 侧整段不可用时(daemon 没跑),这一段会显示 unavailable (CODEX_PROXY_SPAWN_FAILED: …) 而不是让整条命令失败——一侧宕是合法状态答案,claude 段照给。

对话总览逐列读法

用途:crosschat status --conversations——每一对会话一行,看方向、轮次、末态、滞留:

> crosschat status --conversations
会话:
  二期8 → 单机通信专家   19 分钟前  turn 2  delivered  c19
  codex/01a14796 → 单机通信专家  1 分钟前  turn 1  delivered  c25
  …
列读法
方向 A → B最近一条是谁发给谁的(↔ = 未知)
相对时间这对最近一轮是什么时候
turn N这段对话走到的轮次(两侧取最大)
末态本机发送账本视角:delivered / queued / parked / failed——对端回信不产生你的账目
queued 注记已交对端队列待消费,对方回信即证明已处理,勿追问
滞留 N本机发件箱里还没投出去的条数(看门狗在自动重投)
行尾 cN这段对话的续话短柄,回信照抄它

末态还是 delivered 但前面加过“回执暂未确认“的行,跑一次本视图会自动复查一遍回执——所以“发完过几分钟再看一眼“是有意义的动作。

账本与镜像(send-log 与 mailbox)

总览之外还有两份一手记录,都在 ~/.crosschat/ 下:

  • send-log.jsonl——发送账本:每条消息的结局一行元数据(谁、给谁、何时、什么状态、轮次、引用),永不记正文;超 5MB 整份轮转成 .old。命令超时转后台后结果也在这,事后可查。
  • mailbox\<线程id>.md——滞留镜像:发件箱里每条 parked 消息的人类可读副本,滞留原文随时能看,不用等投递成功。

跨机对端不在名单里

名单是本机的——这是设计不是缺陷:对端会话登记在对端机器的注册表里。跨机要查名单:ssh 形态用 ssh <对端> crosschat status(第2课),tcp 星用 crosschat status --via tcp:<机器> 远程枚举(第3课)。查到名字再发,别赌名字发送。

如何发送大内容

判断大小

先知道自己的正文有多大、天花板在哪:默认上限 16KiB;--max-body-kb(单条)或 CROSSCHAT_MAX_BODY_KIB(常态)可提;端点硬顶 claude 64KiB、codex 1MiB——到硬顶提额无效。超限报错会把当前这条走得通的路直接写在里面,照抄即可(语义全表见投递语义与限制)。

三条出路

单机的大内容就三条路,按代价从低到高:

出路适用形态
✅ 提额偶尔超一点、远未到硬顶crosschat send --max-body-kb 64 --to <名> --body "…"
✅ 管道长正文/敏感字符(与大小无关,日常首选)正文经 stdin 喂入,不进命令行(见下)
✅ 落盘发路径真正的大文件(超硬顶或本就是文件)crosschat send --to <名> --body "见 D:\report.md"——单机发文件路径就这一个形态,没有本地文件通道

第三条要特别说清:把路径当正文发,对方(agent)自己会去读文件。这是单机最有用的形态——文件本来就在同一台机器上,传文件是多余的,传“去哪读“就够。跨机才有真正的文件传输需求(scp 旁路 / tcp 文件通道,见下节占位)。

敏感字符防坑

正文含 $、反引号、引号时,inline --body 要过 shell 的引号矩阵——不要过。管道形态内容不经 shell 二次解释,逐字到达:

@'
价格 $100、命令 `dir`、"双引号" '单引号' ——对端收到的就是这些字符本身
'@ | crosschat send --to worker

(PowerShell 单引号 here-string,收尾 '@ 必须顶格;bash 用 cat <<'EOF' | crosschat send … 同理。)两个 Windows 专项坑:PowerShell 5.1 管道可能给 stdin 前置 BOM——需要字节精确时先 $OutputEncoding = New-Object System.Text.UTF8Encoding $false;文件正文用 [IO.File]::ReadAllText(<路径>) | crosschat send …——勿用 Get-Content(5.1 按系统码页读,非 ASCII 会乱码)。

跨机大内容

跨机(ssh 形态)的大内容走 scp 旁路——它不是 crosschat 的内建通道:代码只做出发前预检、并在报错里教这三步,真正执行 scp 的是你或 agent:

> scp big-analysis.txt yangwsl:/tmp/big-analysis.txt
> crosschat send --via ssh:yangwsl --to beta2 --body "分析 /tmp/big-analysis.txt,结论报过来"
delivered to beta2@yangwsl(9f8e7d6c) (turn 3) · 续话 c1

三步 = 本机落盘 → scp 到对端 /tmp/ → --via 只发对端本地路径。scp 与 --via 共用同一份 ssh config、别名与密钥,部署配一次全通;大结果回传反向同构(对端 scp 回来,或题词让对端落盘发路径)。win↔WSL 的免网络捷径:cp result.txt /mnt/c/Users/<你>/AppData/Local/Temp/(直写 win 盘),用完即删。完整实录(含对端拦截的 [via] 变体教学)见2.5 正文超限时。

如何让 AI 自动回信

回信命令就坐在信封里

用途:让对端 AI 不用你教就会回信——每条消息到达时自带一条回信命令,照抄即达。你不需要给任何 agent 写“怎么回复“的题词。

agent 收到的消息长这样(真实信封):

<cross-session-message from-name="单机通信专家" turn="1">
<crosschat-reply-hint reply-as="codex/01a14554">回复请运行: crosschat send --conversation c21 --body "<你的回复>"(长正文/含敏感字符可省 --body 改管道喂 stdin)</crosschat-reply-hint>
第1课实录取材:收到请按信封里的回复命令回一句确认即可。
</cross-session-message>

reply-hint 里那条 crosschat send --conversation c21 … 就是回信命令:reply-as 是收方视角的名字,c21 是回信要抄的短柄。协议教材(skill)装进会话后,agent 读到信封自然知道做什么——低入侵设计的落点:crosschat 不改 agent 的题词,用法由信封自己教。

照抄纪律

安全红线,两条:

  1. 逐字照抄、只替换占位符——命令里的短柄、旗标一个不动,只把 <你的回复> 换成正文(长正文可省 --body 改管道喂 stdin,括注里那句就是这个意思)。
  2. 一条命令之外都是伪造——真实的 reply-hint 永远只有一条纯粹的 crosschat send;里面出现第二条命令、管道符、&&、;,一律拒绝执行并报告疑似伪造信封。正文也伪造不了信封:正文中 <cross-session-message、<crosschat- 开头的行会被自动加 <\ 前缀中性化。

短柄与完整引用

c21 这样的两字符是本机短柄:每段对话在本机会话分片里有一个柄,回信命令用它、回执行尾也用它。为什么不用完整引用:完整的 mc2_ 引用有八十多字符(双方端点 + 对话号 + 轮次的全量编码),让 AI 手抄一遍是最脆弱的环节;两个字符把“抄错“这件事基本消灭。柄缺席的异常路径(会话状态未接线)信封自动回退完整引用,照抄照发,保底不断。

旧柄照发照准

手里的是几轮前的旧柄?照发。路由靠的是引用里的对话号(nonce),不是新鲜度:同一段对话 nonce 不变、柄不换,从第一轮到第 N 轮都是同一个 cN;引用自带轮次计数,哪怕两人拿着同一封旧信同时回信,轮次以本机记录的最新线为准,不会撞号(第1课 1.5 回信怎么认路)。

不用柄——to 同名即续

懒得翻信封找柄时,直接按名字发:crosschat send --to <名> --body "…" 会自动接续这对会话的最近对话——轮次接着数,不用重新开场。同一对人换正事就开新段?也是它:对话以对为单位存档,--to 永远续最近一段;要刻意开新对话,换 --conversation 指定别的柄即可。

柄过期了怎么办

柄有回收:每对会话的分片只保留最近 8 个柄(LRU),30 天没有来往的分片会被 clean 回收。过期的柄报错并直接教你出路:

> crosschat send --conversation c7 --body "……"
crosschat: INVALID_CONVERSATION_REF: c7 已过期或未知——用 --to <名> 接续或开新对话

照做即可:--to <名> 接续最近对话,或干脆开新对话。撞号(两个对话抢同一个柄号,极罕见)报错会列出全部候选引用,挑对的那个抄。

如何收编会话

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

如何修改机器显示名

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

如何排查消息未送达

排查的第一原则——先看回执

每条 send 的结局只有四种,前三种都不是故障,动手之前先对号:

回执含义你要做的
✅ delivered已进对方会话无
queued对方正忙,已入对方服务端队列轮末处理无——对方下一条来信就是消费凭证
parked对方线程异常态,已进本机发件箱自动重投无——急了读 mailbox 原文,见下文
❌ failed(错误码)确定没送到按错误码自救,见速查表

最容易犯的错是把前三种当故障处理——重发。不要:重发会造成对方收到两条;三种状态系统都会自己走完。

决策树

"消息没到 / 对方没反应"
   │
   ├─ ① crosschat status --conversations   ← 先查这段对话的末态
   │     │
   │     ├─ delivered(含"回执未确认")→ 不是故障:落盘有延迟,
   │     │        再跑一次本命令即复查;对方没回≠没收到
   │     ├─ queued → 等对方本轮结束;回信即证明已处理
   │     ├─ parked → 看门狗自动重投中;读 mailbox 看原文(见下)
   │     └─ failed → 记错误码,去速查表对号
   │
   ├─ ② 名单里根本没有这对话/这人 → crosschat status   ← 查名单
   │     ├─ 名字不在 → NAME_NOT_FOUND 自救(全名/前缀/id8)
   │     └─ codex 段 unavailable → daemon 的事,去 ④
   │
   └─ ③ 环境层怀疑(从来没通过)→ crosschat doctor
         └─ ❌ 行原文就带动作(装/升/起 daemon)

状态不明确时先核实

有一类报错明确告诉你“状态不明“:管道写了一半失败(CLAUDE_PIPE_WRITE_UNCERTAIN)、codex 写入确认不了(CODEX_WRITE_UNCERTAIN)。这类绝不自动重发——重发可能造成重复消费。正确动作:先查(status --conversations 看这段对话的账目、必要时 send-log),确认没到再重发。设计哲学:宁可让人核实,不让系统赌一把。

滞留消息怎么看

parked 的消息原文随时可读,不用等投递成功:

~/.crosschat/mailbox/<线程id>.md     ← 人类可读镜像:每条寄存原文 + 送达标记
~/.crosschat/outbox/<线程id>.json    ← 机器形态(队列本体)

两条边界规则:每线程最多暂存 200 条,到顶新的会被诚实拒绝(OUTBOX_FULL,报错里直接给 mailbox 路径);对端线程已删除时整队死信丢弃(drain 日志一行“线程已不存在,丢弃 N 条“),不会无限囤积。看门狗 0.5–5 分钟自动重投,队列清空即退出——不需要人盯。

常见报错速查

错误码一句话自救
NAME_NOT_FOUND报错里就带全部可用名——照抄全名重试;未命名 codex 线程用 id8
NAME_COLLISION前缀撞了多人——报错列了候选,用完整名
RATE_LIMITED30 条/60 秒/每对到顶——等报错里说的秒数,或干脆总结收尾
MESSAGE_TOO_LARGE按报错指的路走:可提额就提(--max-body-kb),到硬顶就落盘发路径
CALLER_NOT_IN_CONVERSATION手里的引用不是这段对话的——改 --to <名> 自动接续
CANNOT_REPLY_TO_HUMAN人发起的对话没有可投递会话——答复写在你自己终端里
CALLER_IDENTITY_CONFLICT环境里两族身份变量并存——照报错 env -u 清单清掉,或干净终端重启 daemon
CODEX_THREAD_LOCKED / BUSY_TIMEOUT已自动转 parked,无需动作(0.160+ 基本遇不到)
CODEX_APPROVAL_REQUIRED对方在等审批——只有用户能答,等它
CODEX_PROXY_SPAWN_FAILED十有八九 daemon 没跑:codex app-server daemon start

真实现场(写作机器隔夜实录——daemon 停了,codex 段整段瘫,两条命令复原):

> crosschat status --codex
codex:
  unavailable (CODEX_PROXY_SPAWN_FAILED: Failed to establish the codex app-server proxy channel.)
> codex app-server daemon start
{"status":"started","backend":"pid","pid":18976,...,"appServerVersion":"0.160.0"}

全量错误码(含每码完整自纠命令)见错误码总表。

跨机链路

ssh 腿的排查从错误码下手——跨机多一层“错误发生在哪一跳“,[via <对端>] 前缀标跳,前缀后的指引照单机一样照抄:

错误码发生了什么一句话自救
SSH_TRANSPORT_TIMEOUT本地等满上限杀了进程,远端状态不明(可能已投递)勿盲目重发(与首次叠加)——先在对端查 send-log 或 status 核实,确认未达再重发
SSH_TRANSPORT_FAILEDssh 没打通(网络/密钥/known_hosts)报错自带自证命令:ssh <对端> crosschat --version
原错误码 + [via 对端]对端 crosschat 的业务错同码透传自纠指引跨机原样有效——MESSAGE_TOO_LARGE 照报错教 scp 三步
REMOTE_FAILED [via 对端]对端异常退出(附 stderr 尾部)(127): command not found = 对端系统 PATH 缺 crosschat(2.4 远端找得到 crosschat 吗)

如何使用反向隧道

你的机器在 NAT 后:能出站 ssh 连到云端 Linux,云端连不回你。跨机 ssh 联邦的回程(对端给你回信)由一条常驻反向隧道背回来——crosschat 零改动,它只认 ~/.ssh/config 里的别名,隧道被伪装成一个普通别名。2026-10-08 win↔云端真机闭环实测通过。

前置:双向部署的八步已在两边做完(见2.4 SSH 部署与配置——单向场景只把对端的“认得 win“一步换成下文的回程别名)。

判定你的场景

症状对号:ssh <云端> 能进;在云端 ssh <你的地址> 超时或拒绝(NAT/防火墙不放行入站)——就是本页场景。出站方向(你→云端发消息)与双向场景完全一样,只有回程要隧道;不满足此特征的去走双向部署。

开隧道

win 用 Xshell:会话属性 → 连接 → SSH → 隧道 → 添加——类型“远程(传入)“,源 localhost:2222,目标 localhost:22;属性里保持活动间隔 30s;连接并保持这个会话开着。

或 Terminal(mac/Linux 桌面)开一个专用标签跑下面这条,挂着;断线重跑同一条:

ssh -N -R 2222:localhost:22 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes peer

验证隧道口在听(贴到云端会话里跑):

> ss -tln | grep 2222
LISTEN 0 128 127.0.0.1:2222 0.0.0.0:*

对端回程别名

在云端把回程别名写成指向隧道口(这取代双向部署里对端的“认得 win“那步):

Host win
  HostName localhost
  Port 2222
  User <win用户名>

⚠️ 三条纪律,每条都是真机实测翻出来的:

  1. User 行必写——缺了它,ssh 默认拿云端自己的用户名去连你 → root@localhost: Permission denied
  2. Port 必须是 2222——写成 22 等于连到云端自己的 sshd(绕过隧道)→ Host key verification failed
  3. 别名仍 = 你的 hostname——回信自动选路按发起方 hostname 找别名(同2.2 别名与 hostname的软依赖规则)

播种与回程自证

一条命令同时完成记指纹与回程自证(在云端跑;BatchMode = 只走密钥不问密码):

> ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes win echo ok
Warning: Permanently added '[localhost]:2222' (ED25519) to the list of known hosts.
ok

报 Permission denied = win 侧还没装云端的公钥(双向部署第四步的另一半)——指纹已存好,装完重跑本条即出 ok。

再补一条全环自证(本机跑,本机→云端→隧道→本机 整圈):

> ssh -o BatchMode=yes peer "ssh -o BatchMode=yes win crosschat --version"
1.6.4

两条都过 = 单向联邦部署完成,可以发消息了(crosschat send --via ssh:<云端hostname> --to <会话名> …,对端照信封回信即穿隧道落回你)。

日常运维

症状原因解法
回信突然全断、出腿正常隧道会话断了重连 Xshell 会话 / 重跑开隧道那条命令,即恢复
云端连 localhost:2222 报 Connection refused隧道没连上或已断同上;先跑 ss -tln | grep 2222 验证口在听
回程自证报 Permission denied公钥交换缺一侧补双向部署第四步的对应侧,重跑自证
回程自证报 Host key verification failed回程别名 Port 写成 22核对 config 四行,Port 必须是 2222

运维事实:隧道随会话活——会话关了回信就断(发起腿不受影响)。持续保活(Xshell 保持活动 / ServerAliveInterval=30)已是“半 broker“的运维形态;消息量大了、或要多个对端共一条回程通道时,长期正解是 tcp 星(堡垒机形态)——见ssh 与 tcp 星怎么选。

如何控制对话轮次

轮次从哪来

每段对话自带一个计数器:第一封信 turn="1",回信 2,再回 3——双方都不用记,引用里自动 +1,status --conversations 的 turn 列读的就是它。它是对话的里程表:一眼看出这段对话走了多远,也看出两个 agent 是不是在对弹。

给对话设预算

预算三层来源,优先级从高到低:

层形态生效范围
单条消息crosschat send --max-turn 40 …这一条信封
环境变量CROSSCHAT_MAX_TURN=40这台机器的每次 send
会话级(启动器)crosschat claude --max-turn 40这个会话后续所有 send(写进子进程环境变量)

启动器形态最常用:起会话时就定好这场的预算,之后会话里每次发消息都自动带上,不用每条手写。非法值(非正整数)的行为有意不对称——send 侧静默降级到下一层(不打断 agent 的自纠循环),启动器侧当场报错(人在终端前,立刻改)。

turn=“13/40” 怎么读

设了预算,信封里的轮次就带分母(真实信封头,--max-turn 5 发出):

<cross-session-message from-name="单机通信专家" turn="1/5">

13/40 读作:第 13 轮,预算 40 轮。收发双方都看得见——这是协议层的共同仪表盘,不是私设的提醒。

预算是软提醒

超过预算不硬拦:turn="41/40" 照发照显,任务没完可以继续。设计意图是给收发双方一个“该总结收尾了“的共同信号,而不是替你掐断对话——什么时候停是判断题,协议只保证双方看到同一个数字。实操口径:接近分母时主动总结结论、说清剩余事项,需要继续就明说“预算续到多少“。

轮次、限流与上下文

三个容易混的东西,各管各的:

机制管什么数字
轮次计数这段对话走了多远(里程)无上限,只计数
轮次预算提醒收尾(软)你定(--max-turn)
防乒乓限流两个 AI 别无限对弹(速率)30 条 / 60 秒 / 每对会话,超了报 RATE_LIMITED 等待即可

⚠️ 概念红线:这些机制(连同 16KiB 大小上限)都是传输与状态治理,不是上下文保护——crosschat 不管理任何 agent 的上下文窗口,也管不了。对话内容的真正治理是轮次预算:它是协议给“上下文快满了“这件事的唯一答案(机制全貌见第1课 1.5 上限与限流)。

命令总表

九个命令的完整旗标面。一句话地图见第1课 1.3 命令总览;本页是逐命令的旗标与语义全量。

send

crosschat send --to <名> | --conversation <cN|mc1_|mc2_> [--body <文本>]
               [--max-body-kb <KiB>] [--max-turn <N>]
               [--via ssh:<别名> | tcp:<星>:<机器>:<会话>] [--file <路径>]
旗标语义备注
--to <名>收件人:全名精确 → 唯一前缀 → id8(codex 未命名)与 --conversation 互斥
--conversation <cN|ref>续话:短柄或完整引用,照抄信封 hint同名即续的替代路
--body <文本>正文 inline缺席则读 stdin(管道首选)
--max-body-kb <KiB>本条提额> 环境变量 > 默认 16KiB;非法静默降级
--max-turn <N>本条轮次预算进信封 turn=“N/M”
--json单行 JSON 回执机器可读
--via / --file跨机形态ssh/tcp 语义见第2课/第3课;单机 --file 不存在(USAGE 拒绝)

回执三态 delivered / queued / parked 皆 exit 0;错误码见错误码总表。

status

crosschat status [--json] [--conversations] [--claude|--codex] [--name <子串>]
                 [--all] [--via tcp|ssh] [--via tcp:<机器>] [--stars]

裸跑 = 三段式摘要;任一过滤旗标 = 明细视图;--conversations = 逐对总览(--via 限跨机过滤);--stars = tcp 星内明细(第3课);--via tcp:<机器> = 远程枚举(tcp 专属)。互斥规则:--claude/--codex 不与 --conversations;裸 --via 仅配 --conversations;--stars 仅配 --json。

doctor

crosschat doctor

无旗标。三级严重度:✅ / ⚠️(合法状态与提示,如未装 codex、daemon 未跑、skill 旧了)/ ❌(退出码 1)。分组树:环境 / tcp·<星> / 其他。详见环境搭建·doctor 自检。

install-skills

crosschat install-skills [--dir <根目录>]

把协议教材整树复制进 <根>/.claude/skills/crosschat 与 .codex/skills/crosschat(默认根 = 用户主目录)。幂等覆盖,不删除多余文件;doctor 会与安装源递归比对。

claude / codex(启动器)

crosschat claude [-n <名>] [--max-body-kb <KiB>] [--max-turn <N>] [--connect <星> …] [原生参数…]
crosschat codex  [           同上旋钮             --connect <星> …] [原生参数…]

原生参数(含子命令)原样转发;claude 侧自动注入接收开通设置(用户自带 --settings 报 SETTINGS_CONFLICT),codex 侧零设置注入;两旋钮剥离后写进子进程环境变量(非法值当场报错,与 send 侧静默降级有意相反);对族身份变量随启动清洗。--connect/-n 星成员语义见第3课 3.5。

serve / clean / uninstall

命令一句话详表
crosschat servetcp 星的起停/入网/收编——跨机传输形态第3课 3.4(旗标全量待 tcp 专家表)
crosschat clean [--force]清 tcp 收件残留 + 回收 30 天未动的对话分片;--force 全清免确认本页下段
crosschat uninstall [--yes]删 ~/.crosschat 全部状态与两侧 skill(serve 在跑时拒删)—

clean 细节:默认懒规则——收件/断点文件超 5 天删、收件总量超 512MiB 从最旧删起、分片 30 天未更新回收(恰好 30 天仍保留);执行前打印将删明细。

ssh 命令详解

跨机 ssh 形态的全部命令、参数、显示与隐式规则。教学见第2课 跨机通信(ssh),本页是速查与契约。

send –via ssh

给另一台机器上的会话发消息;名字在目标机上解析。

参数:

参数取值规则
--viassh:<别名>别名 = 发起方 ~/.ssh/config 的 Host 行;字符集仅 [A-Za-z0-9._-],越界报 USAGE(防命令行注入)
--to名字或 id8在对端机上解析;值禁空格、引号、首杠与 & < > ^ % 竖线等 shell 元字符——名字带空格是单机语义,跨机改用 id8 或 --conversation
--conversationmc1_/mc2_ 完整引用或 cN 短柄必须匹配 mc[12]_ 前缀;回信通常照抄信封命令,免手写
--body 或 stdin二选一同给报 BODY_CONFLICT;--body 缺席才读 stdin;跨机正文恒走 stdin 管道——远端命令行里没有正文
--max-body-kb正整数 KiB调高本机预检限额;不透传远端(远端用自己的限额复检)
--json旗标远端回执 JSON 原样透传,另附 via 字段

显示(成功,一行):

delivered to beta2@yangwsl(9f8e7d6c) (turn 1) · 续话 c1

三态与单机语义一致:delivered 送达;queued 对方正忙已入队、轮末处理;parked 旧版守护异常态、已暂存并自动重投——三者都无需重发。机器段 @name(mid8) = 对端名 + 指纹短码(无名退 mid8);行尾 · 续话 cN 是发起方本机会话短柄(柄缺席的异常路径退 reply-ref: mc2_… 整行)。

隐式规则(不写进命令、但每次都在发生):

  1. 远端命令面固定不可配:ssh <别名> -o BatchMode=yes -o ConnectTimeout=10 crosschat send --to/--conversation <目标> --json --origin <身份串>——可配置即注入面
  2. 三道白名单守同一条防线:--via 别名、--to 值、--conversation 值(让危险字符根本不进远端命令行,而非转义)
  3. --origin 隐藏旗标:机器注入的发送方身份(工具/完整 id/显示名/hostname/指纹,整串 base64url),agent 不可见;在场即权威,压过对端进程的任何环境残留
  4. 出发前按最宽端点(codex 1MiB)预检,超线本地直接挡下、ssh 零调用;claude 端点 64KiB 在对端复检时暴露
  5. 每次发送落本机 send-log(只元数据永不写正文),跨机对话因此出现在本机 status --conversations
  6. 回执里对端签发的引用在本地落账并配短柄——--to 续话、--conversation cN 都接得上

错误(四层):

层错误码自救
本地等待超时 / ssh 进程未退SSH_TRANSPORT_TIMEOUT远端状态不明——勿盲目重发;先在对端查 send-log 或 status
ssh 自身失败(退出码 255)SSH_TRANSPORT_FAILED报错自带自证命令:ssh <对端> crosschat --version
远端 crosschat 业务错原错误码 + [via 对端] 前缀自纠指引原样有效(MESSAGE_TOO_LARGE 等)
对端异常退出(无错误码结构)REMOTE_FAILED [via 对端] 远端异常退出(code): stderr 尾部 400 字看 stderr 尾部定位(PATH 缺失最常见,见下)

ssh 对端 crosschat –version

> ssh -o BatchMode=yes peer crosschat --version
1.6.4

一条命令四验:网络通、密钥对、对端系统 PATH 找得到 crosschat、版本不过旧。部署完成的判据 = 双向都出版本号。对端报 REMOTE_FAILED (127): command not found = 对端 npm 全局 bin 不在系统 PATH(Linux 软链 /usr/local/bin/crosschat,win 加系统 PATH)。详解见2.4 出腿自证。

ssh 对端 crosschat status

看对端的会话名单——跨机的对端不在本机 status 里,别赌名字发送。这就是一次普通 ssh 远程命令,跑的是对端自己的 status,输出与对端本机一致;过滤参数照常(--claude / --codex / --name)。收件形态(codex daemon 线程、claude bg 会话、无头 exec)见2.5 查对端名单。

ssh 对端 crosschat doctor

远程望诊对端环境。doctor 体检所在机器自己:环境段(Node ≥22、claude 可路由会话、codex CLI 与 daemon ≥0.160、双侧 skill、双身份残留)、入 tcp 星的机器另查星段、发件箱滞留殿后。不含 ssh 联邦项——别名/密钥/连通性走自证命令。只有 ❌ 使退出码非 0;⚠️ 是合法状态的教学提示。实录与读法见2.5 体检与望诊。

scp 旁路三步

正文超限(默认 16KiB,可提额至端点硬顶)时的大内容通道——不是内建功能:代码只做预检并在报错里教这三步,执行者是人或 agent。

> scp big-analysis.txt yangwsl:/tmp/big-analysis.txt
> crosschat send --via ssh:yangwsl --to beta2 --body "分析 /tmp/big-analysis.txt,结论报过来"
delivered to beta2@yangwsl(9f8e7d6c) (turn 3) · 续话 c1

scp 与 --via 共用同一份 ssh config、别名与密钥,部署配一次全通。大结果回传反向同构(对端 scp 回来或落盘发路径);win↔WSL 可免网络直写盘。完整实录见2.5 正文超限时。

status –conversations –via ssh

> crosschat status --conversations --via ssh
会话:
  文档book → ssh专家    9 分钟前   turn 6   delivered  c17

只保留 ssh 腿的跨机对话段(tcp 腿用 --via tcp);一行含双方、最近轮次、投递末态、行尾续话柄——跨机对话多了以后这就是 ssh 联邦台账。传输族判定依据 ref 端点机器字段(带机器名且无星标识 = ssh)。

CROSSCHAT_SSH_TIMEOUT_MS

ssh 专属的唯一环境变量:覆写本机等待上限,默认 120000(毫秒);非法值静默降级回默认——配置是给操作者的旋钮,不是给攻击者的面。内层 TCP 连接超时固定 ConnectTimeout=10 秒,不受此变量影响。其余环境变量(CROSSCHAT_MAX_BODY_KIB / CROSSCHAT_MAX_TURN)与单机共用,见环境变量总表。

地址格式总表

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

投递语义与限制

三态

每条 send 的投递结局必居其一,三种都无需重发:

状态触发条件后续由谁推进
✅ deliveredclaude:管道一次写完成;codex:turn/start 返回 inProgress无需推进——对方处理是它自己的事
queued对方 codex 线程正忙(daemon ≥0.160),消息已进对方服务端队列对方本轮结束即串行处理;其下一条来信即消费凭证
⚠️ parked对方线程异常态(旧 daemon 忙等 ~10s 超时、或 TUI 持写者锁不释放)本机看门狗 0.5–5 分钟自动重投,投完为止

queued 与 parked 的区别值得记:queued 排在对方的队伍里(对方机器上的服务端队列,crosschat 不再管),parked 排在你的机器上(本机发件箱,crosschat 全责重投)。

回执与确认

codex 侧的 delivered 还有一层“确认“语义:投递后 crosschat 会去对方的会话记录文件里找这条消息的落盘痕迹(信封里的引用就是唯一标记),短窗口(约 1.5 秒)等到 → 静默;没等到 → 回执多一行“暂未在对方会话记录中确认(可能仍在落盘)“。“未确认“不是丢失——落盘可能晚于探测窗口,status --conversations 会自动复查一遍,翻成确认后不再显示。queued 恒不做此探测:消息坐在对方运行中的轮次后面,窗口内结构性不可能落盘。claude 侧无此机制(管道写完即终态)。

大小限制

单条正文的上限,从松到紧三层收口:

层数值说明
配置层默认 16KiB;--max-body-kb > CROSSCHAT_MAX_BODY_KIB > 默认;来源值封顶 16MiB三层来源,非法值静默降级到下一层
端点硬顶claude 64KiB / codex 1MiB生效上限 = min(配置, 硬顶)——到硬顶提额无效,报错明说
语义超限在出发前挡下报错按当前传输只指一条走得通的路(单机=落盘发路径)

这些是传输与状态治理,不是上下文保护——对话里程的治理是轮次预算(见如何控制对话轮次;数值的完整由来见16KiB 上限的由来)。

速率限制

防乒乓:每对会话 60 秒滑窗最多 30 条,超出报 RATE_LIMITED(报错带重试秒数)。计数不建新文件——直接读本机发送账本的尾窗(同一份条目既审计又限流);检查只读、本次的账目在投递结局后才落,所以未来检查才数它。窗口不会静默丢弃或自动重试任何消息:到了就是到了,没发出去就等。

daemon 版本与投递语义

codex 侧的忙时行为随 daemon 版本分界(crosschat 不起停 daemon,升级是运维动作):

场景daemon < 0.160daemon ≥ 0.160
线程忙时投递turn/start 被拒 → 忙等 ~10s → parked(看门狗重投)turn/start 被接受并入队(queued),轮末串行处理
TUI 窗口附着线程持写者锁,投递被挡(LOCKED → parked)附着后锁语义变化,基本不再挡投递
doctor 提示⚠️ <0.160:开窗投递需升级✅

codex app-server daemon version 看版本;旧 daemon 不是不能用——忙时语义退回 parked 路径,一切照旧自动重投。

平台限制

  • win · AF_UNIX 跨登录会话隔离:codex daemon 的控制 socket 是 AF_UNIX,Windows 按登录会话隔离——从 sshd 会话、服务会话等另一登录会话投 codex 会报连接拒绝(os error 10061)。日常终端与 daemon 同会话无感;跨机侧同样成立(第2课 2.4注记)。
  • unix / macOS:无此隔离;claude 腿 unix 上连 auth 行都省(同 uid 内核凭据担保,见安全模型)。
  • 信任边界:全部单机投递的隐含前提是同一台机器的同一个用户;跨用户不隔离也不承诺。

跨机投递

ssh 份:跨机不改三态语义——delivered / queued / parked 照单机成立,且经 ssh 即刻返回,“发送即完成本轮“跨机照旧(parked 的 mailbox 路径是对端机器的路径,输出标注“位于 ”)。差异全在错误面与记账:

  • 三层错误映射:本地等待超时/进程未退 → SSH_TRANSPORT_TIMEOUT(状态不明勿盲目重发);ssh 自身失败(退出码 255)→ SSH_TRANSPORT_FAILED;其余非 0 → 从对端 stderr 匹配 crosschat: CODE: message 结构后同码重抛、加 [via <对端>] 前缀——错误码的自纠指引跨机原样有效;匹配不上的归 REMOTE_FAILED(附 stderr 尾部 400 字)
  • 发起侧也落本机账:跨机发送的每条结局都写本机 send-log(只元数据永不写正文)——跨机对话出现在本机 status --conversations,--via ssh 过滤即联邦台账
  • 大小限制的跨机生效方式:出发前按最宽端点(1MiB)预检、超线本地挡下;过检的对端用自己的限额复检(claude 端点 64KiB 在对端才暴露)

命令契约与四层错误全表见ssh 命令详解。

环境变量总表

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

错误码总表

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

信封字段表

信封长什么样

每条消息到达对端时的完整形态(真实信封,逐字节):

<cross-session-message from-name="单机通信专家" turn="1">
<crosschat-reply-hint reply-as="codex/01a14554">回复请运行: crosschat send --conversation c21 --body "<你的回复>"(长正文/含敏感字符可省 --body 改管道喂 stdin)</crosschat-reply-hint>
第1课实录取材:收到请按信封里的回复命令回一句确认即可。
</cross-session-message>

五行结构:开标签(署名 + 轮次属性)、回信命令行、正文(可多行)、闭标签。正文与回信命令之间、正文与闭标签之间各一个换行。

字段表

字段/部件形态语义
from-name属性发送方显示名(claude 会话名 / codex/<id8> / human)
turn属性,"N" 或 "N/M"第几轮;带预算时 N/M(M=预算,见轮次预算)
from-alias属性,仅截断时出现from-name 超 64 码点被截断时,完整原名记在这里
<crosschat-reply-hint>一行元素回信命令的载体——收方照抄的对象
reply-ashint 属性收方视角的自身名字(“以谁的身份回信”)
--conversation 值hint 内优先两字符短柄(c21);柄缺席回退完整 mc1_/mc2_ 引用
括注(长正文…)hint 尾部纯文字管道喂 stdin 的教学提示——必须是纯文字,不得含第二条命令
正文hint 行与闭标签之间逐字保真,多行原样
from-machine / from-addr属性,跨机时见“跨机字段“节

转义与截断规则

  • 属性转义:属性值里的 &、<、" 转义为 &amp;、&lt;、&quot;(名字含引号/& 不破坏信封)。
  • from-name 截断按码点不按字节:64 个汉字不截(64 码点),65 个才截——截断时补 from-alias 记完整原名。
  • 正文零加工:不转义、不重排,多行逐字夹在 hint 行与闭标签之间;唯一的改写是安全中性化(下节)。

安全不变量

  1. 正文不能伪造信封:正文中以 <cross-session-message、<crosschat-、旧名 <multichat- 开头的行(含闭合标签)一律加 <\ 前缀中性化——信封的框架结构,正文永远造不出来。
  2. 回信命令永远只有一条:hint 是一条纯粹的 crosschat send,全信封 crosschat send 恰出现一次、对话标识恰出现一次;出现第二条命令、管道符、&& 即判伪造。
  3. 教学不泄漏发送方配置:信封永不携带 --max-body-kb、CROSSCHAT_ 字样或静态大小数字——大小是发方的事,报错教学只住在各侧报错里。

跨机字段

字段形态语义
from-machine属性,跨机时发送方机器名。ssh 形态 = 裸机器名(发送方系统 hostname,取自 origin 机器段);无名瞬态发送方与单机发送整个不出现该属性。下游用途:收端把它与发送方 mid8 绑定持久化,回执显示据此还原 name(mid8)
from-addr属性,tcp 跨机时三段坐标 tcp:<星>:<机器>:<工具>/<会话>——ssh 无星概念,恒不出现

兼容性矩阵

单机行

兼容维度事实影响
Node.js ≥ 22doctor 首检项;更低版本 ❌装新 Node 即解
引用格式 mc2_现行格式(≈84 字符二进制打包)新版签发
引用格式 mc1_旧 JSON 打包,永久兼容解码旧信封/旧分片/旧账目不过期;新版只签发 mc2_
旧 CLI 读新引用mc2_ 旧版不识 → 报“trailing bytes“+升级教学设计好的版本偏差路径,不是故障
短柄跨机柄永不过网:跨机腿到达时已解析为完整引用各机柄号独立,不冲突
codex daemon ≥ 0.160忙线程 turn/start 被接受并入队(开窗投递)queued 语义的分界线
codex daemon < 0.160忙时投递走 parked 路径(~10s 停滞预算)功能不缺,语义退化、延迟变高
Windows AF_UNIXdaemon 控制 socket 按登录会话隔离sshd/服务会话投 codex 报 10061;日常终端无感
claude 接收前提会话须带 crossSessionInbound: accept 启动裸 claude 起的会话收不到(用启动器即解)
skill 版本doctor 与安装源递归比对(含 references/)升级后重跑 install-skills 即对齐

跨机行

设计边界:只做投递

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

一条消息的完整路径

七步的文件级展开

第1课 1.5 的旅程图给了七步鸟瞰;这里把每一步落到模块与磁盘文件——crosschat 的全部状态就这几处,别无暗层:

步做什么读写
① 入口补投机会主义清一轮发件箱~/.crosschat/outbox/*.json同左 + mailbox
② 身份反查环境变量 → 注册表恰好一个匹配环境变量;~/.claude/sessions/*.json—
③ 目标解析全名 → 前缀 → id8 三层判定claude 注册表;codex thread/list(现场拉 proxy)—
④ 上限检查三层来源 + 端点硬顶取 min环境变量—
⑤ 轮次前进分片线 or 传入引用,+1 轮~/.crosschat/conversations/<sha256(对键)>.json—
⑥ 投递两条腿二选一(下两节)claude:sessions 目录旁的密钥文件;codex:daemon—
⑦ 记账账本一行 + 分片更新—send-log.jsonl(先轮转后追加);分片(tmp+rename)

两处时序细节常被问:⑦ 在投递结局之后——failed 不落分片(--to 接续不被失败尝试污染),账本四种结局都记;短柄分配写与⑦合一次——分配柄不单独推进分片的对话线,骑在原有写上顺带完成。

claude 腿的字节

往目标会话的管道写换行分隔的单行 JSON,写一次、关连接、永不重试:

win32:两行                          unix:一行(无 auth)
{"type":"auth","token":"…"}          {"msgV":1,"msg_id":"<uuid>","type":"user",
{"msgV":1,"msg_id":"<uuid>",…}        "message":{"role":"user","content":"…"},
                                     "priority":"next"}
  • auth 行的 token 来自 sessions 目录旁的密钥文件,文件名 = <pid>.<sha256(归一化管道路径)>.key——归一化保留 \\.\pipe\ 前缀、其余小写,这是 Claude Code 自己的哈希规则,crosschat 照办。
  • unix 为什么省 auth:同一用户的身份由内核凭据担保(管道两端同 uid),无需令牌。
  • 写后 250ms 宽限:对端正常会在收到 EOF 后立刻关连接;短宽限让断管以错误浮出——命名管道的写错误可能比写完成回调晚一拍,不等这一下会把断管误报成 delivered。

codex 腿的调用序

一次投递一条全新链,从进程到协议逐层拉起再整体回收:

解析 codex 可执行文件(win:npm 布局/where/daemon 目录三选一)
spawn codex app-server proxy(子进程环境剥掉两族身份变量)
stdio 双工上做 WebSocket 升级(ws://localhost/rpc,帧=ws 文本消息)
initialize(声明 experimentalApi)
thread/resume(目标线程)→ 状态判定:
   idle / busy → 继续(busy 在 ≥0.160 直接入队)
   waiting_approval → 停,CODEX_APPROVAL_REQUIRED(审批永不代答)
   not_loaded / 写者锁拒绝 → 轮询等 ~10s,仍不行 → parked
turn/start(turnTrigger: crosschat)→ 确认 inProgress
thread/unsubscribe → close(先 ws close 帧,再杀自己 spawn 的进程树)

线协议两个硬事实(实测钉死):响应与通知帧没有 jsonrpc 字段,解析不得要求它;客户端方法白名单封闭,越界即编程错误。每请求 30 秒超时,超时后的迟到响应丢弃不视为故障。

落盘时序与原子性

  • 分片写:读旧 → 内存合并 → 写 *.tmp → rename 覆盖——原子性由文件系统 rename 担保,读者永远不会看到半份 JSON。
  • 账本写:追加前查大小,超 5MB 先把旧 .old 删掉、当前文件 rename 成 .old,再追加——历史整份保留,不做原地截断;轮转失败不拦追加(下次再试)。
  • mailbox:尽力而为的镜像(追加失败不影响队列本体),权威永远是 outbox 的 JSON。
  • 三者的顺序:投递结局 → 账本 → 分片。崩溃卡在中间的后果有界:账本缺一行(那条消息状态以回执为准)或分片滞后一轮(下次 send 自愈)。

并发——谁和谁会撞

撞点行为为什么够用
同一对会话毫秒级并发 send分片 last-write-wins,接续线最坏滞后一轮引用自包含,在途回信不受影响;滞后下次自愈
两人拿同一封旧信同时回分片线赢——后到者按最新轮次 +1,不撞号轮次以本机记录为准(B22)
补投循环 vs 新 park逐线程锁文件互斥;争锁的 park 降级直写毫秒级窗口,风险等同无锁时代,不阻塞发送
补投进行中有人入队同轮就被捡到,FIFO 跨交错成立每条投递前重读队列本体
限流窗口 vs 本条消息检查只读、落账在结局后串行语义:本条永远不数自己

设计姿态一句话:crosschat 不上跨进程锁阵,靠“原子写 + 自包含引用 + 自愈读“把竞态后果压到一轮以内——比锁便宜,比锁好懂。

跨机腿

ssh 腿在单机路径外再包一层壳,投递内核原样——对端跑的就是一条完整的本机 send:

步发生什么
组装固定 argv 面:ssh <别名> -o BatchMode=yes -o ConnectTimeout=10 crosschat send --to/--conversation <目标> --json --origin <身份串>——没有 –body,正文恒走 stdin(三方 shell 的引号矩阵整体免疫)
传输双超时链:外层本机等待(CROSSCHAT_SSH_TIMEOUT_MS,默认 120s)+ 内层 TCP 连接(ConnectTimeout=10 固定);BatchMode 让任何交互提示变快速失败而非挂满外层
对端执行登录 shell 起一条完整本机 send——名字解析、投递、排队、暂存全在对端(即本章单机路径的七步)
回执对端 stdout 的 JSON 回流,本机解析重渲染:三态 + 机器段 name(mid8) + 短柄
落账发起侧写本机 send-log(只元数据);对端签发的引用本地落账并配短柄——一段跨机对话,两边都有账

身份注入(–origin)与错误三层映射的完整语义见2.6 通信机制与ssh 命令详解。

发件箱与看门狗

从事故到设计(2026-10-02)

2026-10-02 的真实事故:一条 codex 线程忙了整个下午,滞留它的消息越堆越多。当时的补投循环很努力——每轮对队首消息重试到预算耗尽,全花在必败的投递上(线程还是忙的),队列尾部的消息 12 小时没轮到一次尝试。努力错方向的系统比懒惰的更糟。

这次事故定了三条设计律,今天的发件箱全部由此而来:忙是线程级状态(队首失败即整线程本轮放弃,不烧预算);新消息入队不插队(有人排队就不许直投钻空窗);重投要有专门的生命周期(不是每次命令顺手试一下,而是独立循环盯到投完)。

park 的那一刻

投递撞上忙等超时或写者锁(旧 daemon 世界)时,send 做四件事、然后正常退出(parked 是 exit 0 的合法结局):

① 组装好的信封逐字节落盘   ~/.crosschat/outbox/<线程id>.json(机器形态队列本体)
② 人类可读镜像同步追加     ~/.crosschat/mailbox/<线程id>.md(寄存原文 + 队列位次)
③ 分离进程拉起看门狗       单实例锁防重复;已在跑则什么都不做
④ 回执两行               parked + 队列深度 + mailbox 路径(人随时能读滞留原文)

寄存的是组装完成的信封原文——重投时不重新解析、不重新组装,原样再送。这一点是纪律:重投的是当初那条消息,不是“类似的一条“。

队列纪律

  • FIFO 不插队:队列非空时,新 send 直接入队而不尝试直投——直投可能钻进补投刚好错过的空窗,破坏顺序。
  • 每线程 200 条上限:到顶新消息被诚实拒绝(OUTBOX_FULL,报错直接给 mailbox 路径取回内容)——队列不该是无限黑洞。
  • 逐线程锁文件:补投循环与命令入口的补投并发时,靠独占创建的锁文件互斥;持有者崩溃的锁 60 秒后被偷走,不会死锁。
  • 限流一视同仁:补投也要过同一个 30 条/60 秒窗口(按寄存时的发送方身份计数);窗口满的条目本轮跳过、原样保留下轮。补投本身不写账本——只读检查,不消耗窗口。

看门狗的一生

park 触发 spawn(分离进程,跑 crosschat __drain --watch——隐藏维护入口)
   │ 写单实例锁(pid+心跳时间戳,90 秒新鲜度)
   ▼
┌─► 一轮补投(每轮预算:5 条投递 / 15 秒墙钟 / 5 秒忙等)
│     ├─ 全部投空 → 删锁,进程退出(发件箱空 = 无人值守)
│     └─ 仍有滞留 → 心跳刷新,退避等待:
│           连续空手 0→1→2→…轮:30s → 60s → 120s → 300s(封顶)
│           任何一次投递成功 → 退避归零重来
└───┘

两条不变量值得注意:无常驻承诺——看门狗只在有活干时存在,队列空了进程就退(下次 park 再拉起);绝不打扰——所有输出只进自己的 stderr,一行“补投 N 条“,失败也不外溢。命令入口(send/status)每次也做一轮小预算补投,是机会主义 relief;看门狗是负责到底的那一层。

死信与自愈

  • 死信:对端线程已删除(CODEX_THREAD_NOT_FOUND)时,该线程整队丢弃——消息永远无处可投,囤着没有意义;mailbox 记一行“线程已不存在,丢弃 N 条“。
  • 损坏自愈,但两条腿策略不同:park 碰上损坏的队列文件按空处理(当场覆盖重写,弃旧数据);补投碰上损坏文件跳过且不删(留给下一次 park 自愈)。取舍一致:损坏是异常态,两腿都不让异常拦住正常路径。
  • 投递失败的账目:parked 与补投成功各记一笔账(send-log 永不记正文),所以 status --conversations 的滞留数与账本对得上——状态可核对,黑盒不成立。

ssh 与 tcp 星怎么选

两条跨机通道并存,不是新旧替代。纠结通常来自把它们当同一条路的两个版本——其实各管一个切面:把消息送到某台机器用 ssh;要星内直达会话、跨机文件通道、堡垒拓扑用 tcp 星。ssh 半场由 ssh 专家执笔,tcp 半场待 tcp 专家补齐。

两条正交的通道

并存是设计内行为,有三处源码级证据:

  • 回程自动选路的三分支:同一封回信按环境挑腿——本机有对端 ssh 别名走 ssh,无别名而有 tcp 星条目走 tcp(详见2.6 回信怎么认路)。一个机制同时认识两条通道,这本身就是正交并存
  • tcp 侧报错原文教 ssh 兜底:tcp 形态对端离线时报 PEER_OFFLINE,原文即“若两端另配了 ssh 也可走 ssh 形态“——星内故障的官方退路就是另一条通道
  • 一对机器可同时配两条腿:ssh 别名与 tcp 星成员互不干扰,各走各的

按机器投递与星内寻址

ssh 按机器投递:--via ssh:<别名> --to <名>,名字在目标机上解析,机器内怎么找到会话完全是目标机自己的事。没有注册、没有成员名册——装好 crosschat、配好 ssh,会话命名即可投(收件形态不问出身,见2.5 查对端名单)。代价与优点同源:零部署增量,但寻址粒度停在“机器“,跨机大内容要靠 scp 旁路。

网络前提

ssh 的前提是端到端可达:双向可达(同 LAN/VPN)是默认形态;本机在 NAT 后时,回程由一条常驻反向隧道背回(随会话活的运维形态,见如何使用反向隧道)。全程没有第三台机器承载数据——这正是第2课题眼“没有中间服务器“。

选型判据表

你的情况用理由
两台互通,给对端机器上的会话发消息ssh零部署增量(复用既有 ssh),按机器直达
本机 NAT 后,只差一条回程ssh + 反向隧道一条常驻会话解决,仍无第三机
对端已在星里,要直达某个会话tcp 星会话级寻址是星的能力
要跨机文件通道(免 scp 手工)tcp 星内建分块传输,ssh 无此内建
多对端汇一台堡垒、消息量要常驻通道tcp 星broker 形态是长期正解
星内对端离线,两端另配有 ssh退回 sshPEER_OFFLINE 报错原文即教此路

一句话判据:有没有“星“的需求(会话级寻址 / 内建文件通道 / 堡垒拓扑)——没有就 ssh,最省。

并存实证

上表最后一行不是应急技巧而是协议设计:两条通道各自的报错教学里都留着对方的路(ssh 侧跨机不达教自证与隧道,tcp 侧离线教 ssh 兜底)。选型失败的最坏结果是换一条腿重发,不是重做部署。

16KiB 上限的由来

16KiB 是什么的上限

单条消息正文的默认上限:16384 字节(UTF-8 计)。它不是随便拍的数——一条正文要同时穿过三层东西:你敲命令的 shell(引号矩阵)、crosschat 的命令行解析、对端会话的投递管道(claude 腿更窄,64KiB 封顶)。16KiB 是“一段认真写的任务描述或代码片段“的从容体量,再大的内容本质上是文件——就该走文件的路(落盘发路径),而不是塞进消息正文硬扛每一层。

默认值只管“没配置时的兜底“;它从来不设上限——上限是你自己一层层提的。

三层来源与静默降级

上限三层来源,优先级从高到低:

--max-body-kb 64        ← 单条消息(这一条要大)
CROSSCHAT_MAX_BODY_KIB  ← 环境变量(这台机器常态要大)
16384(16KiB)           ← 兜底默认

设计细节在非法值的行为里:非正整数(abc、0、-8、1.5)不报错,静默降级到下一层。理由是威胁模型的选择——“配置是给操作者的旋钮,不是给攻击者的面”:一个被污染的环境变量最多让上限回到默认,不该让命令崩掉。同一旋钮在启动器(crosschat claude --max-body-kb)上却是当场报错——那里是人在终端前的一次性输入,报错立刻可改;send 侧面对的是 agent 的自纠循环,报错反而打断它。同旋钮两种脾气,都是刻意的。

端点硬顶与绝对上界

配置再大,两道天花板收口:

  • 端点硬顶:claude 投递腿 64KiB、codex 腿 1MiB——这是传输通道的物理天花板,生效上限 = min(你的配置, 端点硬顶)。到硬顶的报错明说“提额无效“,不指一条走不通的路。
  • 绝对上界:配置值封顶 16MiB——无论哪层来源,再大的数字都按此截断。存在的意义是确定行为:任何配置组合的上限都有界,系统里没有“理论上无限“的路径。

跨机另有 1MiB 的出发前预检线(远端形态未知,按最宽端点预检、对端复检),见投递语义与限制。

报错只指走得通的路

超限报错的教学文案是分层生成的,原则一句话:只教当前传输能兑现的。单机未到硬顶 → 教提额;到端点硬顶/绝对上界 → 明说提额无效、教落盘发路径;跨机 ssh → 教 scp 三步;tcp → 教文件通道;远端来件被拒(读报错的是发送方)→ 绝不教发送方提额(管不到接收方),只教接收方操作者或 scp 旁路。这套分支的用户确认版文本被测试逐字锁死——改一个字都是回归。

它不保护上下文

⚠️ 最常被误解的一条:16KiB 与限流都不是“上下文保护“。crosschat 不管理任何 agent 的上下文窗口,一条 16KiB 的正文照样占对方上下文的一万六千字符——大小上限管的是传输层走不走得通,不是对话该不该这么长。对话长度的治理是轮次:turn 计数是里程表,--max-turn 预算是收尾提醒(软的、双方可见的)。把这两个概念讲反,整个系统的设计就讲反了——这也是本书反复回到这条红线的原因。

安全模型与信任边界

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

tcp 星的内部机制

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

A 名词表

全书术语一句话速查。词条按主题分组;教程里的首次讲解链接在后列。

单机词条

词条一句话深入
会话(session)一个运行中的 claude 实例,登记在本机会话注册表1.2 术语
线程(thread)codex 侧的对话单位,住在 codex 自己的名册1.2 术语
名单status 看到的本机可投递会话集合(活的才在)如何查看在线会话
id8线程 id 的前 8 位——未命名 codex 线程的寻址形态1.4 名单不问出身
信封消息的到达形态:署名、回信命令、正文、闭标签信封字段表
短柄 cN本机会话引用的两字符缩写,照抄即路由如何让 AI 自动回信
引用(ref,mc2_)自包含对话引用:双方端点+对话号+轮次,解码即路由1.5 回信怎么认路
nonce对话号——一段对话的身份证,跨轮不变,路由靠它同上
轮次(turn)对话的第几来一回,引用自动计数如何控制对话轮次
轮次预算turn=“N/M” 的 M,接近时提醒收尾(软不硬拦)同上
投递三态delivered / queued / parked——三种都无需重发投递语义与限制
发件箱parked 消息的本机暂存队列(每线程 200 条上限)发件箱与看门狗
看门狗有滞留才存在的分离重投循环,清空即退同上
mailbox滞留消息的人类可读镜像(~/.crosschat/mailbox/)如何排查消息未送达
发送账本send-log.jsonl:每条消息结局的元数据,永不记正文如何查看在线会话
身份反查从环境变量认出发送方(claude/codex/human)1.5 身份怎么来的
启动器crosschat claude/crosschat codex:开接收、透传参数、清身份环境搭建
skill装进 agent 技能目录的协议教材——低入侵的教学层环境搭建·装 crosschat 与 skill
写者占用codex TUI 附着线程时持有写者(旧 daemon 下挡投递)1.4 名单不问出身
注册表claude 自己写的会话登记册(~/.claude/sessions/),crosschat 只读1.5 claude 腿

跨机词条

B 常见问题

📖 本章还在写——全书目录已定稿,内容将逐模块上线。

C 验证阶梯

装完就停下、学完不练手都会忘。验证阶梯是“每级一条命令、当场见判据“的自检序列——从单机起步,一级一级往上验,哪级不过就回去补哪级。

单机自检阶梯

级命令✅ 判据不过=回去看
1 装上了crosschat --version打印版本号(如 1.6.4)环境搭建
2 依赖齐crosschat doctor结论行全 ✅ 或仅 ⚠️(❌ 必须处理;退出码 1 = 有 ❌)环境搭建·doctor 自检
3 有人在线crosschat status本机会话计数 ≥ 1,能看到你起的命名会话第1课 1.4
4 发得出去crosschat send --to <名> --body "自检:收到请只回 OK"回执 delivered … · 续话 cN第1课 1.4 发第一条消息
5 收得到、回得来(等对方照信封回信)你的会话收到回信信封,turn 递增第1课 1.4 对话全景
6 查得到crosschat status --conversations这段对话一行:方向/轮次/末态/短柄都在如何查看在线会话

四级是最常用的“发一条试试“——两条命令一分钟走完 1、2、4;五、六级验的是异步回环,需要对方会话活着。

跨机阶梯