按线程隔离
队列按线程隔离:每个 threadId 一条 FIFO,串行链也是每线程一条。
不同线程的 turn 并行执行、互不阻塞。
agent.prompt(),会撞上 Agent is already processing 守卫。现在它进入该线程的
FIFO,由该线程的串行链依次执行。
所以「排队」不是全局的一个盒子,而是每个会话各自的一条队——
你在会话 A 排队不会挡住会话 B。
四个操作
- 没有暂停:派发中的项已从快照消失,无锁定态可言;上一轮流收尾后链节自动取队首开跑
- 没有熔断:失败项随流终结回填成气泡,要不要重发由你决定,而不是让一个自动闸 替你决定后面几条也一起停
早期的问题在哪
队列状态没有持久化、事实源分散、前端对消息数组做多路手术。
刷新重启后排队消息失踪变成 ghost;消息数组要摘除/回填/恢复/抑制四条路径,
还有恢复竞态、对账重复追加、顺序错乱;任意旁路流结束把会话状态打回 ready,
按钮闪烁。
队列重写为纯状态机
QueueEngine——队列、自增 id、派发收口在一个无 I/O 的类里。
每次变更向会话转录追加一行全量快照,重启、切线程、树导航之后从最后一条恢复;
同时向线程广播全量快照,前端「最后快照胜出」。选全量快照而不是增量的理由很朴素:队列通常不超过 5 条,全量最简单、没有对账逻辑,
广播频率也低。
快照怎么走
- 持久化:每次变更向 session JSONL 追加一行
queue_state全量快照 - 恢复:sidecar 重启后经
get_queue_state回放恢复,不再自动暂停 - 广播:每次变更经
sendEventChunk向该线程发data-queue-state - 冲突:前端「最后快照胜出」。线程无活跃请求时快照静默丢弃——空闲态的变更都由 前端自身的 invoke 发起,前端从回复里自更新
「同 id 原地更新多 phase」那种增量 chunk 形态已整体废弃。
入队、位置、派发全部由全量快照承载,没有 per-item 生命周期 chunk。
前端三条同步规则
前端对消息数组的所有操作收敛为三条,每条幂等:
被删掉的旧路径:restore 竞态补偿、cancelled 标记、对账方向分支——全部由
「快照状态 + 三条规则」替代。
稳定 id
条目有持久化的自增 id,跨重启不重复,派发与取消都以 id 寻址。 这不是细节:早期用数组下标寻址,重启后下标错位就会取消错东西。附件不丢
早期快照只持久化文本,刷新后重建的气泡不带附件。后来修掉了——快照条目现在携带 图片附件(协议形状{ name, mimeType, data | path },与 prompt 帧同形),
刷新/重启恢复、接力泵出队重发与队列条缩略图都不丢图。
这里有个值得记的坑:sidecar 原本按一个前端从不发送的
type: "image" 字段判形状,
实际把图片全部丢弃——所以历史 queue_state 行无一携带附件,而且不报错。
教训是:协议形状要对齐已经在用的那一套,而不是发明一个新字段。已知边界
「发送后、确认前」刷新:注册表在前端内存里,刷新即清空。此时那一项按快照重建 (现在含图片附件),可接受。下一步
模式总览
一个应用里五个「模式」,别混。
会话工作区
排队条在对话流里的位置。
