fix(runtime): diagnose the interpreter or runner actually selected - #4647
Conversation
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: cddb58143d29d07e7cc91cf29962ac9c5d90309d (fix(runtime): scope SDK diagnostics to the actual execution source).
动机
一个解释器里缺 DeepSeek Harness SDK,看起来和「整机运行时不可用」是同一句话 dsh_runtime_unavailable;而当注入 runner 时,可用性判定根本没做导入探测却照样报可用。Chat 的拒绝文案又只说 pip install,没说装到哪个环境。后果是操作者在错误的解释器里装 SDK,装完仍然被拒,循环往复。动机成立。
改动思路
不新增探测路径,而是在既有的共享 resolver 上把「这个判定是关于什么的」显式说出来:managed 执行器返回 runtime_probe{schema_version, scope, module, available},scope 区分 probing_interpreter(真的 find_spec 了 deepseek_harness)与 configured_runner(注入 runner,module: None,没有探测);非 managed 执行器同样带这个字段但为 None,这样读者不必靠「字段是否存在」分支。Chat 侧只改文案,走既有错误门指向服务环境。
具体改动
9 个文件、+160/-16,生产改动约 30 行:
loopx/control_plane/turn_driver/host_binding.py:新增MANAGED_RUNTIME_PROBE_SCHEMA_VERSION、RUNTIME_PROBE_SCOPE_INTERPRETER,managed 分支返回 probe,其余分支返回None。loopx/chat_manager.py:manager_channel_binding增加可选module_probe并透传,把 probe 投影进 channel binding。loopx/chat_agent.py:dsh_runtime_unavailable的 next action 改为「服务解释器 /loopx doctor/python.executable/ 在同一个环境安装 / 重启」,且不含解释器路径。docs/integrations/deepseek-harness-connector.md:写明 probe 语义与「可用性只对回答的那个解释器或 runner 成立,且不证明 provider 鉴权」。- 三个 smoke 与两个测试文件:断言 probe 作用域、单机无关性(credential smoke 注入
_runtime_installed)与公共安全(序列化 probe 无路径、无凭据值)。
我复核的关键点(都在这个 head 上自己跑过):
- probe 矩阵(直接调用):无 runner + probe False →
scope=probing_interpreter, module=deepseek_harness, available=False,unavailable_reason=dsh_runtime_unavailable;无 runner + probe True → available True;dsh_runner_configured=True→scope=configured_runner, module=None, available=True;individual host →runtime_probe为None。 pytest -q tests/test_manager_channel_binding.py tests/test_turn_managed_executor_binding.py tests/test_loopx_turn_driver.py tests/test_loopx_turn_executor.py tests/test_loopx_turn_host_failure.py tests/test_loopx_turn_managed_step.py tests/test_chat_manager_context.py tests/test_chat_manager_inspection.py→ 237 passed。examples/loopx-turn-managed-executor-binding-smoke.py、examples/loopx-managed-turn-operator-flow-smoke.py、examples/operator-provider-credential-smoke.py→ 全部 ok/exit 0。- 拒绝优先级未变:缺 SDK 仍
dsh_runtime_unavailable,缺凭据仍operator_credential_unconfigured,run-once --execute仍 fail-closed。
遗留问题(非阻塞,P3)
PR body 的验证段写「两个真实隔离 Python 环境证明 SDK 缺失/存在的 readback」。仓库里提交的 smoke 实际是用 sys.meta_path 的 _HarnessRuntimeFinder 伪造运行时存在性,没有创建环境或拉起第二个解释器。行为本身仍有证据(注入 probe 的矩阵 + 测试),但最吸引这次改动的那个多环境场景没有可复现的仓内检查。最小修法:把两环境 readback 落成 smoke 的一步,或在正文里注明它是人工证据、仓内覆盖靠注入 probe。
对主干的风险
改动面是「一个附加字段 + 一段操作者文案」。最强回归是「注入 runner 却被读成探测过 SDK」,这正是新字段要消掉的信息缺失,现在由 smoke 与测试双向钉住(available True/False 都断言 scope 与 module)。公共/私有边界我也看了:probe 里只有 schema 版本、scope 字符串、模块名与布尔值,smoke 显式断言序列化结果不含 / 与凭据值,解释器路径留给本地 loopx doctor。未验证维度:没有对真实 managed host 或 provider 发起调用(PR 明确把范围限定为 import/readiness),多环境场景见 P3。回退成本一个 commit。
我的整体评价
结论 APPROVE。它把「进程级答案」与「机器级事实」分开,并使用既有 resolver 而不是再造一条探测路径;Chat 的失败不再是无法行动的 pip install,而是点名服务环境、python.executable 与同环境安装。我用四态矩阵、237 个测试与三个 smoke 独立复核了行为与公共安全边界。唯一 P3 是正文引用的多环境证据不可复现。
English verdict: APPROVE - exact head cddb581; the managed executor binding now states what its availability verdict is a claim about (scope probing_interpreter with module deepseek_harness, or configured_runner with module null), the field is an explicit null for every non-managed executor, and the Chat refusal names the service environment, loopx doctor and python.executable. I verified the four-way probe matrix directly, ran 237 binding/manager/Turn tests and three smokes (all ok), and confirmed host selection, credential gating and refusal precedence are unchanged. One non-blocking P3: the PR body's two-isolated-interpreter evidence is not reproducible from the repository, since the committed smoke injects a meta_path finder instead.
A missing SDK in one interpreter must not look like a machine-wide runtime outage. The managed binding now identifies an interpreter import probe separately from an injected runner; runner availability no longer falsely claims the SDK was probed. Chat uses its existing failure gate to direct operators to
loopx doctorin the service environment and installation into that same environment.Replaces #4623 and absorbs the runtime-test seam from #4620. The manager and Turn continue to share one binding resolver. Host selection, credentials, refusal precedence and effects are unchanged; no paths or credential values enter the added probe data. No frontend editor change is needed: the existing Chat error gate already renders the supplied next action, and this change adds no configuration.
Validation: 108 binding/manager/Turn tests; managed-executor, operator-flow and credential API smokes; 24 baseline/candidate combinations preserving every pre-existing binding field. Two actual isolated Python environments prove SDK-absent and SDK-present readbacks using the pinned SDK. Ruff, configured mypy, public-boundary checks, exact-scope quality qualification and standard premerge pass. These are import/readiness checks, not provider-authentication or model-call qualification.
Refactor pass: remove the misleading probe wrapper and reuse the existing environment adapter and Chat error path. Python owns interpreter probing; no second state-machine authority is introduced.