Repository navigation
fix(chat): retire stopped quota failures from pending readback - #7791
loopx-agent merged 1 commit into
Conversation
Signed-off-by: Yuchao Wang <stevenkid404@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max
动机 / Motivation
在飞书私聊中因模型额度不足而失败、随后已停止旧任务或开始新对话的用户。
旧失败请求已经被同一来源的停止或新对话命令取消,后台仍把它显示为待处理;本提交从原始会话、执行轮次和来源控制记录推导待处理状态,重启后旧项保持退出,新出现且尚未取消的额度失败仍会显示。
真实私聊 health 读回在 /stop 和 /new 后从 1 变为 0,后续独立工作和重启保持正确;未来的新额度失败回到 1,其他绑定的停止命令不能清除它。
本 PR 不改变自动重试准入、已接纳恢复的修复优先级或模型执行权限;本地真实存储与传输测试不证明在线飞书或付费模型调用。
改动思路 / Approach
私聊 health 仍先要求原来的失败通知已送达,并核对 binding、来源、消息、Turn 与 Session 的关联。它随后读取 Core 的既有请求记录、原始 Session/Turn 和事件,将事实交给既有 TS conversation binding owner,而不是在飞书适配器里复制业务规则。原本嵌在恢复判定中的来源停止关系被抽成同域 helper,供恢复与观察共用;观察结果不写入第二份取消标记。
这个区别影响实际等待语义:当前无法自动重试,可能只是绑定不可用、退避或达到自动重试上限,不能因此隐藏未完成请求;相反,用户已明确停止或开启新对话后,也不应继续显示旧额度失败。既有 context_recovery 分支、已接纳恢复修复的优先级和执行准入没有因 pending_observation 改变。下表是从源码与测试得出的行为比较,不是界面截图。
| 同一已送达额度失败的后续状态 | 基线计数 | 当前计数 |
|---|---|---|
| 同源后续 /stop 或 /new | 1 | 0 |
| 仅当前不可重试,未取消 | 1 | 1 |
| 旧项取消后出现新额度失败 | 新失败仍应计入 | 1 |
待处理计数是投影,复用 Core 已有来源停止事实并由现有 TS 决策 owner 计算;重试准入继续保持独立,避免以是否能立即重试代替是否仍有未完成工作。
当前边界是停止或替换旧私聊失败后的持久读回,以及新失败继续可见;不增加恢复执行、调度或模型调用。
具体改动 / Changes
审查完整 head c977cfdcb629bbf0bce1987cf630baebb6a0c144,基于不可变 merge base da4510af61277c0d0786fa5aa040ccb01538b1cb。先读取该基线的既有规范,再检查全部生产、测试和文档改动;当前作者修改后的说明不充当独立验收依据。同时核对原始公开审计要求的同源取消、等待保留与无副作用读回。
规范映射:Frontend and Lark views → implemented:Same-audience projections retain canonical lineage and distinguish waiting work from terminal or superseded work.(paired /stop,/new base2fail/head2pass and restart);For private native Chat → implemented:Continuation requires original source identity and explicit lifecycle evidence; unknown availability is not cancellation.(88 Python/35TS + future/new/unrelated/missing-session counterfactuals);outcome-unknown → implemented:Readback must not replay model effects or rewrite retained failed history.(305 transport tests and no-provider/no-recovery observation fences)。
关键代码讲解
-
CoreExternalConversations._source_stop_observed:原本 context recovery 中的停止事实被抽出复用。判定要求完整来源和 binding 匹配,且 /stop 或 /new 不早于原请求;不能用另一个来源的控制消息推断取消。读取的是完整 pending journal。
-
CoreExternalConversations.quota_backlog_pending:新方法载入原始 Session、Turn 和 Turn events,使用既有来源停止事实请求 TS pending_observation。它不调用 provider/binding 恢复入口,也不将无法读取 Session 本身当作完成或取消证据。
-
planBoundConversationRequest:新增观察分支区分“仍待处理”与“现在可自动重试”。匹配的 usage_limit_exceeded 失败在无取消事实时仍 pending;明确关闭的原 Session 或后续同源停止使其退出。context_recovery 的原执行分支继续保持独立。
-
LarkPrivateConversations.health:该实际生产消费者保留已送达失败通知及所有身份关联检查,再委托 Core 获取布尔观察。相同公共计数字段被修正,既有 UI/manager 消费者无需新增配置或第二份状态。
对主干的风险 / Risks
最强反例是旧的停止命令把之后新出现的失败一起清掉,或者同一容器里的其他来源能取消目标请求。独立探针先通过真实私聊入口制造额度失败,完成同源 /stop 或 /new、后续独立工作与重启,再让同一来源产生时间更晚的新额度失败:旧项保持退出,新项计数为 1。随后加入 55 条其他 binding 的控制记录,计数仍为 1。临时让 Session 读回不可得时,已知失败 Turn 不会被当作不存在。对绑定解析和 context recovery 调用设置禁止栅栏后,health 仍成功;观察没有调用模型、恢复入口或改写任何失败 Turn。
PASS:tests/test_chat_conversation_bindings.py 88 项;TS conversation binding 35 项;三个私聊、ordinary project 和 manager returns 模块共 305 项;额外真实存储与来源范围探针 2 项。相同公开回归函数在不可变基线上 /stop 与 /new 两项均失败于 pending 1≠0,当前两项均通过,并覆盖重启和后续工作。Ruff、TS noEmit、配置的 mypy、diff 与双语 RFC diff 检查通过。
标准 premerge 执行 6 个直接检查与 14 个选中检查,13 项选中检查通过,另一项保留为 inherited advisory:control-plane-maintainability-ratchet-smoke.py 报告未修改的 run_host_process 函数过大。我在不可变基线上独立执行同一命令,其完整失败 stdout 与当前 head 一致,检查脚本和因果模块的 Git blob 也完全相同;没有提高预算或删掉该检查。该失败继续记录为失败,修复归原模块 owner,不作为本次待处理投影的回归。作者提到的旧 return-drain 超时在本轮 305 项当前测试中未出现,这不等于证明历史间歇问题已修复。
来源枚举使用完整请求 journal,而非显示上限;没有新增永久字段、模型准入、调度或配额义务。既有全量来源扫描的成本保留,不能从源码直觉推定性能改善或已发生性能回归。当前没有新的开关,也没有以 feature-off 名义掩盖默认读回修复。
语义与 CI 对齐
复用现有会话绑定及生命周期词汇,pending_observation 是同部署内部观察输入。协议新增的中英文段落说明取消与重试等待的差别;保留失败历史、原受众权限、既有退避与尝试上限,未把 pending 说成可执行义务。本轮只使用仓库原生本地验证,没有获取、轮询或等待远端 CI;未从已有 CI 提示推断通过、失败原因或合并权限。
我的整体评价 / Verdict
对停止后旧额度失败仍显示待处理的问题,交付判断为 goal_achieved。长期工作能力与用户体验均为 improved,限于取消旧等待、继续独立工作、重启后保持一致和后续新失败仍可见的实际路径;在线飞书、真实模型采用与长期效率收益仍未验证。新观察字段是本地投影输入,复用已有 TS owner 和来源生命周期语义,没有创建通用 Actor、Task 完成或重试权限。helper 抽取减少了恢复与 health 的规则漂移;没有理由为这个修复引入另一套状态或通用框架。
取消旧等待后可继续独立工作,重启不复活旧项,新失败仍保持可见;不以当前无法重试为完成依据。 已有授权与任务身份可直接复用,不增加确认、导航或重复输入;失败与取消后的读回可核验。
已检查 private_conversations.health、Core 观察入口和协议增补:既有待处理计数字段的语义修复,没有新增页面、控件或布局。
未触及模块的维护性失败仍保留。本次没有修改作者代码、合并或撤销旧 review;发布本结论不授予合并、付费执行、安装或 Goal 完成权限。若 head、来源边界或相关依赖改变,需要复核该新提交。
English verdict: APPROVE for exact head c977cfdcb629bbf0bce1987cf630baebb6a0c144; no blocking finding in the reviewed scope. The independently reproduced baseline maintainability failure remains advisory; live Lark/model adoption is unverified.
A quota-failed private Chat request remained in “Pending execution or reply” after the same source issued
/stopor/new, even though recovery correctly refused new execution. This closes the readback gap reported in the post-merge audit of #7773, independently reproduced on mainda4510af61277c0d0786fa5aa040ccb01538b1cb.The existing TypeScript conversation owner now projects quota backlog from the original Turn, Session closure and durable stop observation. Native Chat shares its source-control reader between recovery and readback; Lark consumes that projection. Genuine quota waits remain visible regardless of backoff, exhausted retries or provider/binding availability. Failed Turns and notices remain intact. Recovery admission and exact admitted-continuation repair are unchanged.
This is a bounded reliability/readback outcome under the overall roadmap’s App/Lark stop-and-recovery journey, not completion of the controller RFC. It uses the built-in native_chat/Core owner and existing Lark extension, with no new capability, provider or persisted state. The adjacent refactor shares the source observation rather than creating a second retirement journal. #7430 still owns contract convergence; merged #7692/#7735 and open #7787/#7789 were checked to avoid duplicating their feature work.
Validation at
c977cfdcb629bbf0bce1987cf630baebb6a0c144:test_app_long_return_keeps_all_parts_and_does_not_repeat_identical_parts, independently fails at the same 10-second drain assertion in a clean base worktree.host_process_transport.run_host_processoversized-function finding (independently reproduced on base).No frontend markup, settings or user flow changed: the existing private-conversation panel consumes the same pending_count. Desktop/narrow visual, real Lark/model, installed-app, full-repository pytest and performance qualification were not run. Extra local lifecycle reads are not claimed as a performance improvement. No merge or deployment is part of this proposal.