Repository navigation
fix(quota): bind the receipted Turn to the scheduler fallback hint - #7955
loopx-agent merged 3 commits into
Conversation
`turn_instance_arg` decided the host shell inline, so nothing else could
ask for the same reference. Move that decision into `turn_reference` and
`is_turn_reference`, and keep `turn_instance_arg` as the string form of
it. A caller that builds an argument vector can now render exactly the
reference the settlement commands use, instead of hard-coding one form.
No behavior change: POSIX still gets `${LOOPX_TURN:?}` and native Windows
still gets `$LOOPX_TURN`.
Validation: pytest -q tests/control_plane/test_turn_instance_arg.py.
Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
The fallback follow-up replays the current decision through
`loopx quota should-run`, but only the ACK and failure hints bound the
receipt's Turn. The fallback kept the projected `${LOOPX_TURN:?}`, so a
native Windows host expanded it to an empty value and `loopx-apply-rrule`
exited with `--turn-instance-id must not be empty`; a receipted decision
also lost the Turn that owns its Todo, capability and settlement
identity.
Bind the receipted Turn exactly like ACK and failure, and otherwise
re-render the reference for the shell that runs the command. `args`
mirrors the value the vector carries, so the advertised command cannot
disagree with what the host executes, and `route_binding` reports whether
the Turn came from the receipt.
Validation: pytest -q tests/control_plane/test_scheduler_fallback_hint.py
tests/control_plane/test_turn_instance_arg.py -> 17 passed.
Scope: one binding branch, five tests, no ACK/failure or settlement
change.
Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
The allocation doc said only ACK and failure hints bind the receipt's Turn. Record that the fallback hint carries the same registry binding, that its `--turn-instance-id` value is the reference the current host shell expands, and what `route_binding.turn_instance_bound` reports. Docs only, no code change. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max
未发现本次 Turn 参数绑定改动的阻塞缺陷。审查 head:c6954820393c4f4132c2d403c4cd0295e14eed54;实际 merge base:f736bd2b06b6f321ee16d0a3d924fc9294a7dd7e。这是绑定修复的 APPROVE,不是完整宿主 fallback 资格或合并授权。
动机 / Motivation
在宿主更新工具不可用时,使用 scheduler fallback 的 Agent 需要重放发起动作的原轮次。
旧命令留下环境变量引用,环境中的 Turn 不同就会重放另一轮;本次把已经知道的原 Turn 直接放入完整参数向量,避免人工辨认和补写身份。
本轮真实 File/SQLite 对照确认了参数绑定和原回执重放;显式关闭 context delivery 的 dry-run 继续产生原 Turn 的 ACK 参数,未修改宿主文件或扣额。
本次不更改调度决策、执行准入或宿主授权,也不声称已经完成原生 Windows、实际 App 更新、模型采用和长期成本资格。
默认 context delivery 下 fallback 读取的 reader 响应兼容问题,以及 Windows 实机和实际宿主应用资格,仍未由本次绑定修复结清。
改动思路 / Approach
复用现有 CLI 运输边界和 Turn 引用 helper;receipt 提供身份,TypeScript 继续拥有调度和准入决策。当前 PR 的边界是 fallback 参数与原 Turn 的绑定,不增加第二份状态或调度 owner。
只修投影中的固定字符串不足以绑定原回执;把整个 scheduler 搬到 Python 也没有必要。绑定器优先使用当前调用的具体 Turn;没有具体值时,只重新渲染已知 shell 引用,保留投影已有的具体值。args 的既有 Turn 字段镜像执行向量,route_binding 是派生诊断,不能产生权限。
这个本来就应成立的身份不变量不是可选策略。保留原 fallback 的激活条件和宿主授权边界,比新增开关或恢复框架更小。原 ACK/failure renderer、原生 scheduler、持久 receipt 与实际宿主写入 owner 均未迁移;两个 App 投影都检查了相同绑定。
具体改动 / Changes
独立依据是修改前的 docs/quota-allocation.md,spec_revision f736bd2b06b6f321ee16d0a3d924fc9294a7dd7e,其中完整 follow-up 向量、原轮身份、宿主更新和 ACK 的先后顺序分别保持独立。以下是条款的审计锚点,不是新增协议:
- same-turn-followup:implemented。真实 guard 返回的 fallback 和后续 replay 保留原 Goal/Agent/Todo/Turn;与错误环境变量对照有区分度。
- host-effect-boundary:implemented。只改变参数运输,实际验证用 dry-run;没有宿主文件写入、ACK 执行或业务扣额。
- ordinary-and-unbound:implemented,有界覆盖。既有
turn_instance_arg测试、无 receipt 的 POSIX/模拟 Windows、已有具体投影和无 App fallback 路径覆盖了共享 helper 的边界;Windows 实机仍未验证。
完整差异四文件 +220/-6:生产两文件 +71/-4,测试 +142,文档 +7/-2。
关键代码讲解
turn_reference/is_turn_reference:收口已有 shell 引用表示;固定 token 集只用于命令运输,不从文字推导 Todo 状态或授权。_bind_followup_turn_argument:有具体原 Turn 时覆盖参数;否则只替换 shell 引用。缺少选项或末尾缺值时补齐,返回最终值供镜像使用。bind_scheduler_followup_cli_routes:沿既有 fallback 分支注入 registry 和 Turn。已有 registry 的短路、ACK/failure 的原构造保持;没有引入新决策分支。- 未变的
_load_scheduler_hint是实际下游消费者,不能把绑定器的单测等同于它或 App 已成功运行。
对主干的风险 / Risks
最强反例是命令字段看似正确,下游仍重放另一个 Turn。相同合成输入、真实 File/SQLite 和原脚本 dry-run 显示:base 使用 ${LOOPX_TURN:?},错误环境变量进入 ACK;head 的具体值不受环境影响,native receipt 为 replayed 且保留原身份,ACK 参数也保持该 Turn。没有执行真实宿主变更或把 dry-run 当安装采用。
有一项必须单列的既有缺口:默认调用返回 context reader 响应,原 _load_scheduler_hint 只寻找内联 scheduler,于是打印 apply_needed=false / rrule=unknown。base/head 都有这个行为;强验证另用明确 --context-delivery off 的具名 CLI 路由,才贯通到实际 dry-run。这里批准的是当前身份绑定增量,不能把最初的退出零写成完整 fallback 成功;reader 消费兼容仍由既有调用方修复。
本轮三个 focused 模块 26 PASS / 6 FAIL;base 为 21 PASS / 相同 6 FAIL。六个完整失败块只归一化精确 fixture/checkout 路径和 pytest 对象地址后,均为 6,401 字节、SHA-256 1eb55323c1d46bba1edf3b5344a5fc8622f2e50561a972b19464655b0509c0fb;出错的宿主绑定测试和对应读取 owner 未改。保留 FAIL,不宣称套件全绿,也不以作者的另一组环境结果覆盖本轮观察。
Ruff PASS。标准 premerge 为 6 direct / 18 selected,gate PASS;其中原 maintainability ratchet 的 raw advisory FAIL 仍保留。独立 CQR 仍保留 required focused 失败,不把它降为 optional;代码评审结论与这个严格质量门分别判断。没有查询、轮询或等待远端 CI。
语义与 CI 对齐
这是既有默认身份不变量的修复,没有 opt-in/default-off 声明或新的通用状态分类。diff-scoped advisory 和完整语义检查通过,不能替代上述反例。没有改通用义务文本、拒绝优先级、配额、租约或结算;fallback 的可用性不等于执行授权。文档同时说明了具体 Turn 与无 receipt 的引用方式;“always reference”的文字应结合下一句具体绑定阅读。
我的整体评价 / Verdict
APPROVE 当前绑定增量,problem_context 为 justified_increment。long_horizon 与 user_experience 在这个边界上 improved:原承诺和身份不再依赖错误的环境变量,沿既有命令执行不需额外确认。剩余默认 reader 消费、Windows 实机及实际宿主应用范围按上文保留,不认证端到端宿主采用。
复用现有 CLI 运输边界和 Turn 引用 helper;receipt 提供身份,TypeScript 继续拥有调度和准入决策。当前 PR 的边界是 fallback 参数与原 Turn 的绑定,不增加第二份状态或调度 owner。
有界整理已应用:共享引用表示和本地参数修改 helper 足够;不需要新版本化 decoder、状态副本或额外任务。已检查 CLI、HTTP、scheduler 投影与脚本消费者,没有渲染页面变更,视觉评审不适用。视觉未验证范围:none;实际 App/Windows 执行另列为运行资格缺口。严格 CQR 和合并 readiness/权限仍独立,未合并、未 dismiss。
English verdict: APPROVE — c695482, limited to the receipted-Turn binding. Real File/SQLite guards and explicit context-off fallback dry-runs preserve the original receipt and ACK identity; the base follows the conflicting ambient Turn. Premerge passes; six unchanged focused failures and strict CQR remain failed. Default reader-response consumption, native Windows and live host application remain unqualified.
问题
当
automation_update不可用而apply_needed=true时,决策会投影app_automation.fallback_hint,让宿主执行:这个 fallback 的唯一用途,就是带着当前这次决策重放一遍
loopx quota should-run(scripts/codex_app_apply_rrule.py会把这个 Turn 原样传给重放查询)。所以它必须拿到"发起这次动作的那条 receipt 对应的 Turn"。CLI 侧的绑定函数
bind_scheduler_followup_cli_routes()却没有覆盖它:ack_hint、failure_hint都会注入 receipt 的turn_instance_id,并写route_binding.turn_instance_bound;fallback_hint分支只补一个--registry就continue,把投影里那份${LOOPX_TURN:?}原样留在cli_args里。结果是两条真实故障:
${LOOPX_TURN:?}:它会把LOOPX_TURN:?当成一个未定义的变量名,展开成空字符串,于是--turn-instance-id收到空值。scripts/codex_app_apply_rrule.py的_scheduler_hint_turn_instance_id()立刻以--turn-instance-id must not be empty退出,RRULE 应用、bound ACK 全部走不到。LOOPX_TURN与 receipt 的 Turn 不同(或未设置),重放到的就不是这条 receipt —— 丢的是 Todo 绑定、capability 与结算身份。问题不在投影阶段:
hint_followup.ts里那个字面量只是投影默认值,权威渲染点应该在 CLI 边界,也就是cli_args这个宿主原样执行的完整向量。复现(改前)
改前:
cli_args末尾仍是"${LOOPX_TURN:?}",route_binding里没有turn_instance_bound,传入的turn-receipt-001被完全忽略。改动
1.
loopx/control_plane/quota/effect_program.py— 把"Turn 引用"的两件事提取成单一来源:turn_reference(turn_instance_id=None):有具体 Turn 就用它,否则返回当前宿主 shell 能展开的引用(POSIX${LOOPX_TURN:?},原生 Windows$LOOPX_TURN);is_turn_reference(value):判断一个 token 是 shell 引用还是具体 Turn(${LOOPX_TURN:?}、${LOOPX_TURN}、$LOOPX_TURN都算引用,具体 Turn 才算具体值);turn_instance_arg()改为复用turn_reference(),行为不变,只是不再各写一份。2.
loopx/control_plane/quota/live_decision.py—fallback_hint的绑定与 ACK/failure 对齐:cli_args里的--turn-instance-id(权威值优先);${LOOPX_TURN:?}逐字节不变,原生 Windows 得到$LOOPX_TURN;--turn-instance-id时补上;args.turn_instance_id一律镜像cli_args里最终生效的值,避免说明字段和实际执行的命令不一致;route_binding增加turn_instance_bound。3.
docs/quota-allocation.md— 原文只写"ACK 与 failure 绑定 receipt Turn",补上 fallback 的宿主渲染与 Turn 绑定说明。4.
tests/control_plane/test_scheduler_fallback_hint.py— 五条用例:cli_args+args,turn_instance_bound=true;${LOOPX_TURN:?},模拟 Windows 宿主得到$LOOPX_TURN,turn_instance_bound=false;--turn-instance-id→ 补上 receipt Turn;${LOOPX_TURN:?}、${LOOPX_TURN})在 Windows 上都渲染成$LOOPX_TURN。验证
EffectRuntimeHostPermissionError)失败,改动前同样失败,与本 PR 无关。ruff check三个改动文件:错误数与改动前基线一致(均为既存 I001/FLY002/UP017/TRY004)。pwsh,原生 Windows 的端到端行为由托管 Windows lane 覆盖。提交拆分
三个提交,每个都能独立通过测试:
refactor(quota): give the Turn reference a single owner—— 只把宿主判断收口成turn_reference/is_turn_reference,行为不变;fix(quota): bind the receipted Turn to the scheduler fallback hint—— 绑定修复 + 五条测试;docs(quota): document the fallback hint Turn binding—— 文档。范围
只改 fallback follow-up 的 Turn 绑定与宿主渲染:一个 helper、一处绑定分支、一处文档、五条测试。不改 ACK/failure 语义,不改结算计划,不改 TS 投影(投影所在 runtime 的平台不等于 Agent 实际执行命令的宿主,且在 CLI 边界统一渲染才能保证
cli_args与args一致)。