Skip to main content

第7章:会话与 Gateway —— 把一套 Agent 变成长期服务

一、Session 的本质:不是聊天窗口,而是恢复单元​

gateway/session.py:149 的 SessionSource、:314 的 SessionContext、:760 的 SessionEntry 和 :1192 的 SessionStore 共同说明:session 不只是 message list。它还包含来源平台、用户/频道/线程、profile、cwd、时间、显示元数据、API sidecar、压缩信息和持久化游标。

一个可恢复 session 至少要能回答:

  • 哪个外部会话对应它?
  • 当前消息历史到哪里?
  • 当前 turn 是否还在执行?
  • 发送到模型的内容是否与显示 transcript 不同?
  • Gateway 重启后能否避免重复投递?
  • 同一个用户从 CLI 和 Telegram 进入时,应该共享还是隔离?

二、从平台消息到 Agent Session​

三、Gateway 为什么不能只是一个 while loop​

gateway/run.py:5432 的 GatewayRunner 组合了 authorization、kanban watcher、slash command 等 mixin,并在约 :23100、:23253 处承载 Agent 执行路径。它必须管理:

  • 多平台 adapter 的启动、停止和重连;
  • inbound 消息去重、配对、权限和 profile 路由;
  • session lock / turn lease,避免同一 conversation 并发写历史;
  • 流式事件的消费、合并、背压和超时;
  • delivery ledger,避免重启后重复发送;
  • graceful shutdown、watchdog、health 和 forensics。

这就是为什么 Gateway 目录接近一个独立运行时,而不是 cli.py 的一个子函数。

四、流式事件:模型事件到平台消息的翻译​

gateway/stream_events.py 定义 ToolCallChunk、ToolCallFinished、LongToolHint、GatewayNotice 等事件;gateway/stream_dispatch.py:40 的 GatewayEventDispatcher 再把这些事件分发给平台或 TUI。

模型流是高频、细粒度、可能半截的;平台消息是低频、有大小限制、需要编辑或分块的。适配层因此要有自己的聚合和节流策略:

token delta → stream consumer → platform buffer
tool start → progress/status
tool delta → long-tool hint / partial update
tool finish → final tool result event
turn end → final message / delivery ledger

不要把 provider 的 SSE event 直接交给 Telegram/Discord。连接层的职责就是把模型时间轴翻译成用户平台可承受的时间轴。

五、Session Store 的持久化取舍​

SessionStore 负责 append、load、prune、import/export、search、rename 和上下文辅助数据。它会保留可恢复的结构,而不是把每次发送前临时拼出的全部内容写进去。

典型的数据分层是:

  • transcript:用户、assistant、tool 的事实事件;
  • display metadata:平台、UI、reaction、展示 kind;
  • sidecar:实际发给 API 的临时内容;
  • runtime state:当前 turn lease、压缩 cursor、delivery 状态;
  • search index:跨 session 回忆和 insights 使用的索引。

这种分层让“可搜索”和“可复现 API 请求”可以同时成立。

六、Profile Routing:一台 Gateway,多套人格/记忆​

gateway/profile_routing.py 和 docs/profile-routing.md 描述 profile routing:同一个 Gateway 可以按 community、channel、thread 或 source 映射不同 profile。每个 profile 可以拥有自己的 persona、memory、toolset、model 和 session namespace。

inbound source
→ platform/chat/thread 匹配
→ profile route
→ profile-specific session + memory + tools

这是一种“共享进程、隔离状态”的折中:减少进程数和连接数,同时避免不同社区的上下文混在一起。

七、并发控制:Turn Lease 和生命周期 ledger​

同一个 session 同时收到两个消息时,最危险的不是 CPU 争用,而是历史顺序和 tool pair 被交叉写入。gateway/turn_lease.py 的 SessionTurnLeaseRegistry 提供 session 级租约;lifecycle_ledger.py、delivery_ledger.py 则记录运行和投递状态。

这类 ledger 的价值在失败时才显现:进程崩溃后,重启逻辑可以区分“已开始未完成”“已生成未投递”“已投递但未确认”等状态,而不是凭时间猜测。

八、平台适配器的最小契约​

gateway/platforms/base.py:2626 的 BasePlatformAdapter 让平台差异集中在:

  • 认证与连接生命周期;
  • 入站事件归一化;
  • chat/user/thread identity;
  • 文本、媒体、reaction 和 typing/status 投递;
  • 平台大小限制、重试、限速和错误。

PlatformRegistry 负责选择启用的 adapter;新增平台时不应复制 session 或 Agent Loop。

九、Gateway 的三条设计结论​

  1. Session 是“恢复单元”,包含事实、元数据和运行账本。
  2. Stream 是内部事件协议;平台适配器消费协议,而不是理解模型 SDK。
  3. 长期进程的正确性依靠 lease、ledger、watchdog 和 shutdown flush,而不只是异常处理。