feat(chat): project the steward channel's session mode and status - #4483
Conversation
The steward channel projected the executor it resolved and the model that follows it, but not the execution mode serving the conversation, so a frontend had to fetch a Session to know whether the channel runs a managed runtime or an attached host -- and a managed endpoint that was ready looked the same as a bound channel. The execution-mode RFC makes the binding, not the endpoint, the transport or the audience, the unit of mode ownership. This adds that projection without deriving one: `manager_channel_session_mode_readback` quotes the Session's own `session_mode` and `status`, reads a channel with no Session as `unbound`, and names a mode outside the closed set as `unrecognized` instead of rounding it to a mode the host may not have chosen. `manager_channel_session` selects the newest resumable row on the channel through the store's own public projection, so the channel readback cannot widen what a Session exposes, and the live Chat capabilities readback carries the result for every frontend. The CORS fixture server now installs the chat store the real startup always installs, because the capabilities readback quotes the channel's Session. Tests cover the quoted mode, the unbound channel, the unrecognized mode, the resumable-row selection across channels, and the live capabilities payload; the channel-binding smoke asserts the same contract. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
The steward-channel readiness row listed "the channel projects neither its mode nor its session status" as not implemented. It now quotes the Session's own mode and status, so the row records that and keeps attached-host parity and the external-audience restriction as the open parts. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed exact head: 610b9d86baf6b3b5693ce7978e9a1a344a542194
动机
管家通道的读回已经能回答「用哪个执行器、哪个模型、谁付钱、能不能跑」,但回答不了
「现在是什么执行模式、这个会话什么状态」。这两个问题在产品上是分开的:一个已经
就绪的托管执行器和一个真正绑定了会话的通道在读回里长得一模一样,前端想区分
就必须自己去取一次 Session,等于把模式归属的规则复制到展示层。
docs/architecture/rfcs/agent-session-execution-modes-v0.md 把模式归属单位定在
binding 而不是执行器、传输或受众上,session_mode 是闭集 managed_runtime /
attached_host,并要求公开读回模式与状态(host admission 第 8 条)。管家通道的
readyness 行此前如实记录为「既不投影模式也不投影会话状态」,这刀就是补这一行。
改动思路
关键是引用而不是推导,并且把「谁拥有这条规则」留在原处:
- 模式与状态都从 Session 的公开投影里抄,投影只决定「模式来源」这一个判断。没有
Session 读作unbound,而不是顺着已解析的托管执行器推断出managed_runtime;
闭集之外的值命名为unrecognized,而不是取整成宿主可能没选过的模式。 - 选择器只做选择。哪些状态算可恢复、Session 公开暴露什么,仍由
ChatSessionStore
拥有,所以通道读回不可能借这次改动扩大 Session 的暴露面。 - 线上 capabilities 直接带这条读回,前端不用再多一跳;这也是这次交付里唯一的入口
变化。
具体改动
loopx/chat_manager.pymanager_channel_session_mode_readback(session):纯投影,三种结果分别对应
session_readback/unbound/unrecognized,session_status一并抄出。manager_channel_session(store, *, channel_id=None, provider="", audience=""):取通道上
最新的可恢复行(list_sessions的排序与公开投影都复用 store)。manager_channel_binding(environ=None, *, session=None)多三个键;docstring 写明
没有 Session 时读作 unbound 而不是猜一个模式。- 闭集沿用
chat_store的CHAT_SESSION_MODE_MANAGED/CHAT_SESSION_MODE_ATTACHED,
可恢复集合沿用RESUMABLE_SESSION_STATES,没有第二份真相。
loopx/chat_server.py:/api/chat/capabilities的manager.channel_binding带上
session=manager_channel_session(self.server.chat_store)。tests/test_manager_channel_binding.py:五个用例——托管执行器 + 接入会话时报
attached_host(证明引用而非推导)、无 Session 为unbound、闭集外为
unrecognized、跨通道与已关闭会话下选出正确行、以及线上 capabilities 载荷
带session_mode=attached_host/session_status=busy。tests/test_chat_server_cors.py:fixture server 补上真实启动一定会装的
chat_store;这是本次生产改动带来的必要配套,而不是顺手改测试。examples/loopx-steward-channel-binding-smoke.py:把这条契约写进守护它的 smoke。docs/architecture/rfcs/harness-selection-dsh-pi-v0.md与.zh-CN.md:对应行记录
已落地部分,并保留 attached-host 对齐与外部受众restricted两个未实现项。
对主干的风险
低。改动是只读投影加一个选择器:不触碰执行、权限、状态写入或迁移路径,
session_mode 闭集与可恢复状态集合都仍由原 owner 拥有,且反证证明「无 Session 不等于
托管模式」这条语义是承重的。行为面只有两点:capabilities 载荷多三个键(前端 schema 是
非严格对象,已有读者按额外键忽略),以及 fixture server 现在带有真实启动也会有的
store。另外 named_string_constants 2051→2054 的 inventory 与产生它的常量落在同一个
commit,语义词汇漂移 smoke 因此保持绿。
一处如实说明的交付边界:前端执行芯片还没有把模式/状态渲染出来。它改的是应用首屏,
按仓库首屏评审门禁需要 owner 预览后再定稿,因此作为独立的展示改动跟进,本 PR 只负责
让数据在契约与线上读回里可用,并在 PR 描述里标为部分交付。
我的整体评价
APPROVE。这是把 RFC 已经写明的模式归属规则接到读回上的一刀,方向是让「模式」有一个
可引用的来源,而不是让每个前端各自推断;没有新模块、新 schema、新 CLI,也没有扩大
通道可读或可改的范围。校验在确切 head 上通过:122 个相关测试、通道绑定 smoke(含新增
mode_readback)、ruff、docs governance、语义词汇漂移,以及 canary premerge 11 项
全过、manual_holds=0;change-quality receipt cqr_cc7041812e4ceb7c5644 对同一
scope_fingerprint 校验为 valid。反证也做了:把无 Session 改成推导 managed_runtime
后 test_a_channel_without_a_session_reads_as_unbound 失败,还原后重新通过。
English verdict: APPROVE — head 610b9d86baf6b3b5693ce7978e9a1a344a542194 makes the steward channel readback carry the Session's own session_mode and status, reading an absent Session as unbound and an out-of-set mode as unrecognized instead of deriving one from the executor it resolved, with the live capabilities payload covered by a test. Validation at this head: 122 related tests, the channel-binding smoke with its new mode probe, ruff, docs governance, semantic-vocabulary drift, canary premerge 11/11 with 0 manual holds, and a valid change-quality receipt. One delivery boundary is stated, not hidden: the dashboard chip does not render the new fields yet, because that changes the app's first viewport and needs an owner preview.
问题
管家通道的读回里已经有「解析到哪个执行器、哪个模型、凭据由谁提供、能不能跑」,但没有「现在是哪种执行模式、这个 Session 是什么状态」。于是
docs/architecture/rfcs/agent-session-execution-modes-v0.md明确把 binding 而不是执行器 当作模式归属单位(session_mode闭集:managed_runtime/attached_host),并要求公开读回模式与状态。管家通道这一行此前记录为「既不投影模式也不投影会话状态」。改动
loopx/chat_manager.pymanager_channel_session_mode_readback(session):引用 Session 自己的session_mode与status,并且只决定「模式来源」这一个判断。没有 Session →session_mode=null+session_mode_source=unbound;模式落在闭集之外 →null+unrecognized,绝不向下取整成宿主可能没选过的模式。manager_channel_session(store):选出这条通道上最新的可恢复 Session 行。可恢复状态集合与公开投影都由 store 拥有,这里只做选择,所以通道读回不会扩大 Session 暴露的内容。manager_channel_binding(environ, *, session=None)增加三个字段:session_mode、session_mode_source、session_status。loopx/chat_server.py:线上/api/chat/capabilities的manager.channel_binding现在带上这条读回,前端不必再单独取 Session。tests/test_manager_channel_binding.py:引用而非推导(托管执行器 + 接入会话 → 报attached_host)、无 Session 读作unbound、闭集外命名unrecognized、跨通道选出可恢复行、以及线上 capabilities 载荷确实带模式。tests/test_chat_server_cors.py:fixture server 补上真实启动一定会装的 chat store(capabilities 读回现在要读通道 Session)。examples/loopx-steward-channel-binding-smoke.py:把这条契约加进守护它的 smoke。校验
均在确切 head 上执行:
python -m pytest tests/test_manager_channel_binding.py tests/test_chat_server_cors.py tests/test_chat_lark_api_contract.py tests/test_chat_startup_isolation.py tests/test_attached_session_broker.py tests/test_chat_dsh_adapter.py -q→ 122 passedpython examples/loopx-steward-channel-binding-smoke.py→ 通过,含新的mode_readback段python -m ruff check(改动文件)→ All checks passedpython examples/docs-governance-smoke.py→ okpython examples/semantic-vocabulary-drift-smoke.py→ ok(named_string_constants2051→2054,随产生它的 commit 一起落地)loopx canary premerge --from-git-diff --goal-id loopx-meta→ passed,selected=11,failures=0,manual_holds=0cqr_cc7041812e4ceb7c5644(fingerprintcc7041812e4ceb7c5644227c131915c52a99401192c400d8d9873feddcfcb2e7)→state=validmanaged_runtime→test_a_channel_without_a_session_reads_as_unbound失败;已还原,套件重新通过。交付边界(部分交付,剩余项已标明)
受影响入口是 Chat capabilities API(前端的数据源)与 RFC 契约。前端执行芯片目前渲染执行器、类型、模型与不可用原因;把模式/状态显示到芯片上会改动应用首屏,按仓库的首屏评审门禁需要先给 owner 预览再定稿,因此这一块不在本 PR,作为独立的展示改动跟进。本 PR 是加法契约:前端 schema 是非严格对象,已有读者不受影响。
对主干的风险
低。改动是只读投影 + 一个选择器,不触碰执行、权限或状态写入路径;
session_mode的闭集与可恢复状态集合都继续由原 owner 拥有。唯一的行为面是 capabilities 载荷多三个键,以及 fixture server 现在带有真实启动也会有的 store。