fix(chat): recover interrupted App turn acceptance - #5139
Conversation
d0da692 to
9fb2927
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
English verdict: REQUEST_CHANGES
Reviewed head: 9fb2927f56ead0a0edca8c903d02c13755464895;immutable merge base: 39f2fd15e45987a01769bd9bd5a4da42e89e12d9。发现两处可复现的 P2 回归,结论来自真实存储/控制器的 base/head 对照,不来自远端 CI 颜色。
动机
App 在接受请求过程中断开或崩溃后,重试应恢复同一个逻辑请求,而不是复制用户消息或重新启动已终结的 Turn。这个范围是有用、完整且可单独验证的交付边界;不需要把整个 App 异步会话 RFC 都完成才交付。但当前 head 会在取消后阻止下一轮,并在 home 身份拒绝前写入 Turn,因此长期持续推进和用户体验均存在回归,尚不能证明这一个恢复切片完成。
改动思路
浏览器数据入口重用同一个 client_turn_id 和请求 body,有限重试;Python 在真实文件锁内采集完整 Turn/消息/事件事实,调用新增 TypeScript planner,再按 prepare Turn → active Session → 用户消息 → queued event → settle capsule 的顺序落盘。planner 决定恢复写入与 dispatch,Python 保留存储和 adapter 副作用权。
临时 _acceptance capsule 保存原始展示文本、附件和身份,不只是可删的投影:第一次消息落盘前,仅凭旧 Turn 不能恢复这份意图。现有 ingress receipt 属于另一种待处理输入,Goal settlement journal 也不属于 Chat Session,不能直接冒充这里的恢复来源。不过 capsule 的退休必须与已有取消/终结状态共同建模;仅凭字段存在就认定“仍待接受”产生了下面的真实错误。
具体改动
完整差异 12 个文件,+2697/-137:三个 App 数据/构建/smoke 文件,六个 Python/TypeScript runtime 文件,两个测试文件及 TypeScript 构建入口。不是 coverage-only:生产 HTTP submit、Session 恢复和 App 发送路径都会调用它。没有新权限或新可选 capability;现有 managed Chat 默认获得恢复和一次自动重试,attached-host 的入队边界仍独立保留。
关键代码讲解
- planChatTurnAcceptance / planNewAcceptance:类型化事实负责同 key 重放、前缀补齐和 dispatch。新请求的 521–522 行 把任何 prepared capsule 作为 active conflict,却未排除已经 interrupted 的旧 Turn。
- accept_managed_turn:锁内读取真实全量存储、应用 planner 写入序列;旧请求不凭分页列表或 UI 摘要恢复。消息与 queued event 的精确一次逻辑由这个路径承载。
- submit_turn:先接受请求再准备 adapter,可以保留可重试的接受结果;但现有 home 身份检查仍在后面的 adapter 准备路径,非可重试拒绝也被推迟到副作用之后。
- _repair_incomplete_jsonl_tail:追加前修补未完成 JSONL 尾部;它也影响共享消息/事件追加路径,已核对 event buffer、cursor 和 retention 等原生测试,而不只测新 planner。
- acceptChatTurn:稳定 body/key 加一次有限重试,区分传输/响应体丢失、503、424 resume_failed 与不可重试 409,并保留 AbortSignal。compiled data-path smoke 通过,不等于完整浏览器用户旅程通过。
对主干的风险
-
[P2] 取消已部分接受的 Turn 后,残留 capsule 永久拦住下一轮。 在真实
append_event(kind="turn.queued")已写入turn.queued后注入 OSError、重新打开 Store,再调用 controller 的 interrupt_turn:旧 Turn 变成 interrupted,Session 为 ready、active_turn_id 为 null,但 capsule 仍在。不同 key 的下一轮在 base 正常创建,在 head 抛出指向旧终结 Turn 的 active conflict;原 key 在 base 重放 interrupted,在 head 报 acceptance state inconsistent。健康取消路径和先完成恢复再取消路径均通过,说明问题是持久化前缀与取消交叉,而非普通取消。最小修复应在既有锁和类型化状态边界退休/收束终结 capsule,保留终结重放且不 redispatch;不要简单忽略所有 capsule 或放宽身份冲突。补充“写入各前缀后取消 → 同 key 重放/新 key 继续”的真实存储回归测试。 -
[P2] home 不匹配在拒绝前已经消费 Session。 原生
test_restart_home_mismatch_refuses_before_resume_or_turn_write在 immutable base 通过,在 head 失败。错误仍是 codex_home_mismatch,也没有启动上游 adapter;但 Session 已由 ready 变 busy、active Turn 已写入,消息/事件也已落盘。相同错误码不代表无副作用拒绝契约仍成立。应在 durable acceptance 前验证捕获的 Session/host 身份和不可重试 admission 条件,同时保留 transient adapter failure 的接受后恢复。重跑该原生测试及 accepted adapter-start retry 测试。
语义与 CI 对齐
本 PR 扩展现有 Chat 请求接受语义,不创建更广的 Agent/Goal authority。对齐来源是已有 home 拒绝测试、取消后可继续的生产路径,以及当前 App RFC 的 durable accept/dispatch/correction 边界;没有把后续 RFC 展望升级为新审批门槛。typed-state 缺口是将 capsule 字段存在误分类为可恢复 queued 请求;修复应留在现有 TS 判定 owner,而不是新增文本指令。Chat 专用错误名与领域匹配,写入/dispatch 是机器执行后果,不是 guidance。
本地验证保留 passed/failed/untested 区分:
- 新 TS acceptance 19/19;Python active-turn 47/47。另十个相关 Python 文件 59 passed / 1 failed,唯一失败为上述 home 回归;独立故障探针验证上述取消反例。
- control-plane typecheck、改动 Python Ruff、App retry smoke、desktop build、diff whitespace 和 12 文件 public-boundary 扫描均通过。标准 premerge 的 18 个选定检查及 5 个直接检查通过,但不能推翻负例。
- 全量 TS 为 3156 passed / 1 failed / 30 skipped。失败为未改动路径上的 NoKV “native/done current eligibility supersedes history”,isolated base/head 同一个 case 均通过,因此本次不能确认其因果或宣称既有故障;单独记录为 unresolved,不把它包装成第三个 PR 代码缺陷。30 个跳过属于无隔离 PostgreSQL 配置的可选 suite,本次 Chat 文件存储改动不触及该 authority backend。未查询或等待远端 CI。
我的整体评价
REQUEST_CHANGES。问题本身值得解决,原始意图 capsule 和单一 TS 判定边界有必要;量级与完整恢复链路相称,不能按新增行数机械否决。面向后续改动的 bounded refactor 建议是集中“可恢复 queued / 已终结 capsule”的类型分类与退休,不扩张为另一套通用 journal。旧持久 Turn/无 capsule 重放仍有真实兼容消费者,不能为减代码删除。
长期推进与用户体验判断都为 regression:用户取消后看到 ready 却不能继续,以及身份拒绝留下 busy 请求,均抵消正常重试的收益。请先修复两条现有契约并补实存储交叉负例;再复核全量 NoKV 失败,保留其独立归因。完整浏览器交互、真正进程强杀和跨机器恢复本次未验证,不能以 planner 单测或 build 代替。head 不变时上述结论有效,后续改动需要重新核对 exact head。
| if (facts.preparedTurn.kind === "ambiguous") { | ||
| return rejected("durable_state_conflict"); | ||
| } | ||
| if (facts.preparedTurn.kind === "single") { |
There was a problem hiding this comment.
[P2] Retire terminal acceptance capsules before rejecting a new Turn
在真实 Store 的 append_event(kind="turn.queued") 已落盘后注入 OSError,再重开 Store 并调用 interrupt_turn:旧 Turn 已 interrupted,Session ready 且 active_turn_id=null,但 _acceptance 仍存在。这里把它直接判为 active_turn_conflict,导致下一条不同 client_turn_id 的请求永久被旧终结 Turn 拦住;同 key 重放又被 planPreparedAcceptance 的 queued-only 检查拒绝。immutable base 下相同新 key/终结重放都通过,head 下都失败。请在现有锁和 typed acceptance owner 内收束/退休终结 capsule,保留终结重放且不 redispatch,并补充各 durable prefix 后取消 × 新/旧 key 的真实存储回归覆盖;不能简单忽略所有 capsule 来放宽冲突边界。
There was a problem hiding this comment.
Fixed at exact head 0a6ac76f7. interrupt_turn now serializes with submit/resume, completes the original durable acceptance from its capsule before the terminal transition, and never dispatches that repaired Turn. The TS planner models terminal capsules explicitly: an exact retry validates and repairs the durable prefix, retires the capsule, and returns dispatch=terminal; terminal capsules are excluded from the live prepared-Turn conflict census, so a new key can proceed. Added real-store coverage for all five write boundaries, each with direct retry and interrupt-before-retry, asserting same-key terminal replay has no dispatch and a new key is accepted. Focused results: Python 10/10 prefix cases; TS acceptance 22/22; all Chat Python 430/430.
| session = self.loopx_mode.activate_tools(session, work_dir=work_dir, objective=objective) | ||
| adapter = self._ensure_adapter_locked( | ||
| session, | ||
| accepted = self.store.accept_managed_turn( |
There was a problem hiding this comment.
[P2] Validate permanent home admission before durable acceptance
submit_turn 现在先调用 accept_managed_turn,再由 _ensure_adapter_locked 执行既有 _check_codex_home。当 Session 记录的 Codex home 与控制器不同,错误仍为 codex_home_mismatch,也没有启动 adapter,但 Turn/消息/事件已经写入,Session 已变 busy 并持有 active_turn_id。原生 test_restart_home_mismatch_refuses_before_resume_or_turn_write 在本 PR 的 immutable base 通过、head 失败,证明原本拒绝前无副作用的契约改变。请把 captured Session/home 等不可重试 admission 验证放在 durable acceptance 前,同时保留 transient adapter failure 的接受后恢复;重跑该测试及 accepted adapter-start retry 测试。
There was a problem hiding this comment.
Fixed at exact head 0a6ac76f7. submit_turn now reloads the Session while holding _session_adapter_lock and runs _check_codex_home before LoopX tool activation or accept_managed_turn, preserving a side-effect-free permanent identity rejection while leaving transient adapter startup failures after durable acceptance. The native test_restart_home_mismatch_refuses_before_resume_or_turn_write passes, as do the accepted adapter-start retry coverage and the full 430-test Chat Python set.
0a6ac76 to
0b92887
Compare
|
Updated exact head:
Exact-head local evidence:
|
0b92887 to
e8aec33
Compare
|
Main advanced again while CI was queued. Rebased without patch drift onto |
huangruiteng
left a comment
There was a problem hiding this comment.
动机
本次重新审阅 exact head e8aec3379ab77f5f3d169d3a4149459f6b21a088,没有沿用上一次的判断。我以 #5064 的 App-first 交付方向及 App conversation RFC 的「accept/retry/restart 回到同一逻辑 Turn」来判断这个修复,而不是要求它完成全部会话平台。旧实现先写多个文件再返回响应,中断会留下 busy/queued 状态且丢失原请求;用户侧需要手工清理才能继续。这个 PR 在持久恢复上确有改善,但实际 App 入口的中途 I/O 故障仍不能自动走进新恢复路径,所声明的完整恢复切片还没有闭合。
改动思路
决策集中在 TypeScript chat.turn.accept planner:Python 从 canonical Turn、Session、完整 transcript/events 收集事实并执行计划,不把 UI 有限视图当作全量状态。新的 private acceptance capsule 先保存不可从缺失 transcript 推导的原请求意图,再有序完成 busy ownership、稳定 user message、queued event,最后退役 capsule。这样恢复依靠同一 key 和真实 durable facts,不是仅在内存里 rollback。
正向路径是「App 固定 body/key 的最多两次 POST → HTTP → runtime admission → store 排他锁 → typed planner → 五个持久步骤 → dispatch」。已有 terminal、冲突和 home admission 边界需要继续拒绝,不能为恢复而放宽权限。新 head 对上次的 capsule 取消问题和 home mismatch 先后顺序都补了测试,我分别验证通过;本轮的新阻塞发生在错误分类与实际 App retry 的衔接处。
具体改动
关键代码讲解
apps/presentation/dashboard/src/data/chat.ts:794的acceptChatTurn在循环外序列化 body,复用 key、attachments 和 abort signal。:830–838 只重试 transport unavailable、5xx、424/resume_failed,409 和一般 400 不重试;这个边界本身合理,但必须和服务端可恢复故障分类一致。loopx/chat_store.py:747的accept_managed_turn在真实文件锁内读取 canonical facts,交给 planner,再执行 durable prefix repair。user message ID 固定,complete queued append 可识别,不会在相同请求重放时重新制造一条消息。JSONL tail 修复区分完整未带 LF 的合法记录和不完整尾部。loopx/control_plane/turn_driver/chat_turn_acceptance.ts:711的planChatTurnAcceptance统一新接受、prepared 恢复、已 settled 重放等决策,冲突/损坏拒绝而不是猜意图。terminal capsule 修复后退役,不重新取得 active ownership,也不重新 dispatch。loopx/chat_runtime.py:884的submit_turn将 home admission 放到接受写入之前;接受与 worker dispatch 分开,resume 暂时失败不应抹掉已接受 Turn。done event 也覆盖 queue preparation failure,避免 wait 先返回 terminal 而 Session 尚未释放。loopx/chat_server.py:659的_session_turn是实际 HTTP 错误 owner。其通用异常分支仍调用默认 400 的_send_error;它与新的 App retry 规则组合,正是下面的缺口。
13 文件,2994 additions / 177 deletions;其中 TS planner 743 行、Python bridge 315 行,另有大幅场景覆盖。这个量级不是单纯换一个默认值,但多文件写入无法只靠进程内 rollback 可靠恢复;capsule 的原始意图在 transcript 尚未落盘时确实不可推导。未来改动友好性检查认为 typed planner 的集中所有权有价值,当前最有价值的 bounded companion 是补齐 HTTP 错误合同和真实 App 集成回归,不是再加一套恢复框架。
对主干的风险
[P2,阻塞] 持久接受过程中可恢复的 I/O 异常被服务端编码为 400,实际 App 因而不重试,留下不能直接继续的 busy/queued Turn。定位到新增 retry 分支 chat.ts:830–838,并沿未改动的 HTTP 通用异常分支确认因果链。
我使用构建出的真实 acceptChatTurn、真实 HTTP handler/runtime/file store 和真实 TS planner,注入一次「先真正追加 turn.queued,再抛 OSError」:App 请求状态只有 [400],结果为失败;store 已 busy/queued,capsule 还在,user transcript 一条,worker 启动零次。同 key 的手动重试得到 202 / created=false / 同一 Turn,而普通新 key 得到 409。现有 UI 每次 sendChatTurnStreaming 调用会生成新的 key,接受未返回时也没有已接受 Turn 可供 UI 正常续接,因此这不是后端“能手动恢复”就足够的用户旅程。
最小修复是在 HTTP owner 把接受持久化/可恢复 runtime I/O 分类成可重试的 5xx 或等价 typed recovery response,再让现有有界同 key retry 接上。不要 blanket 重试所有 400,更不要放开 409、validation 或 home admission。仅把该合成 I/O 分类成 503 的受控对照中,同一个真实 App 自动产生 [503,202] 并返回 created=false,说明该修复边界可行;这不是已实现的修复。请增加 App↔实际 HTTP/store 集成回归,至少覆盖 transcript 写入前与 queued append 后,并断言一个 Turn、一条 user message、一个 queued event、一次 dispatch,以及 invalid/409/home refusal 仍不扩大重试或写入。
基线/当前 head 同样存在通用 400 分类,所以我没有把它说成新引入的主干回归;它是当前 PR 明确承诺的 App 恢复结果尚未完成。前缀 transcript 故障对照也证明有效增量:immutable base 手动同 key retry 返回 original request unavailable,当前 head 返回同一 Turn 的 202,但 App 自动路径仍缺错误分类。
语义与 CI 对齐
本地 focused Chat suite 63 passed;完整 TS suite 3256 passed、30 个可选 PostgreSQL skip;typecheck、App retry smoke、packaged dashboard build、CI 范围 ruff/mypy 通过。风险 canary 为 5 direct checks 加 18 selected checks,全部通过。没有查询远端 CI,也没有拿非本 PR 造成的红 CI 发 Request changes。
完整 Chat 诊断首次为 408 passed / 2 failed,两项失败发生在 Chat 创建前的 delegation acceptance revalidation;同一 immutable base 的 targeted suite 2 passed,head targeted 为 1 failed / 1 passed,再串行重跑 head file case 通过。不能因此标成“基线已有无关失败”,也不能宣称完整 Chat suite 全绿;归因仍未彻底解决。我的阻塞结论依赖上面单独复现的真实入口 I/O 问题,不依赖这两个间歇诊断。typed capsule、精确 identity/状态判断、domain-neutral error 和 authority 排除都已核对;本 PR 不授予 Goal/quota/lease 或 attached driver 的新权限。
我的整体评价
持久前缀恢复、terminal capsule 退役和 home admission 的修正值得保留,也没有发现需要另外引入通用框架的理由。可是 long-horizon 的「故障后继续有用工作」和 user experience 的「由实际 App 入口自动恢复」在一个可恢复故障上仍未实现,不能把后端成功与 mocked fetch smoke 当作完整交付。请补 HTTP 错误边界及真实 App 集成验证后重新审阅,同时保留上述完整 Chat suite 的 passed/failed 区别。这个评审覆盖整个当前 head,而不是只检查上次两条评论。
English verdict: REQUEST_CHANGES - Durable prefix recovery is improved, but recoverable acceptance I/O failures still return HTTP 400 and strand the actual App caller instead of reaching its same-key retry path.
e8aec33 to
8e61631
Compare
|
Updated exact head: The blocking App/HTTP retry gap is now closed:
New DCO-signed commits:
Exact-tree evidence:
The targeted manifest suite remains 7 passed / 2 failed on the same eight pre-existing validation findings reproduced on |
|
Exact-head CI is complete for The four red checks reduce to two pre-existing manifest tests plus their aggregates:
Both tests report the same eight registry-I/O findings as the |
8e61631 to
8d64afd
Compare
|
Main advanced to Post-rebase checks passed:
The intervening main commit only changes issue-fix metrics vocabulary files and does not overlap this Chat patch. CI has been retriggered. No merge action taken. |
huangruiteng
left a comment
There was a problem hiding this comment.
Exact reviewed head: 8d64afd8adc8cdbb8510d29a559b395d76516161; immutable baseline: 9eaacfbf2ff93d5386cee82ddf0847d9c739e78a.
发布前发现 head 更新后,我核对了两段 commit range:四个 PR 提交的 patch 全部相同,是无差异 rebase;随后在上述新 head/base 重新执行了 Python、TS、真实编译 HTTP smoke、故障恢复 oracle 和静态检查。下文结论绑定新 head,没有沿用旧 head 的验证身份。
动机
评审依据是 #5064 和 App conversation RFC 的 Acceptance / Dispatch / Managed 行:响应丢失或接受过程中断后,同一个请求必须能恢复,不能产生第二次执行,也不能永久卡在 busy。本次不是只看上轮修正;我重新检查了完整 PR。最新两个提交补齐了真实 HTTP 错误分类和 App 调用,属于有实际用户收益的完整恢复切片,但不等于整个异步 inbox 或 attached-host 生命周期已经交付。
改动思路
App 继续发送原来的 scoped client_turn_id 和相同请求体;HTTP、runtime、file store 再经过 typed TS planner 判定接受、恢复或冲突。Session/Turn/原始 transcript/event 是事实来源,Python 负责读取和执行计划,TS 拥有决策;没有新增 Todo authority 或通用队列。短暂 _acceptance capsule 保留尚未完整写入的原始请求意图,完成各持久步骤后退役,不能用一份缺失的 transcript 猜测用户请求。
真实 smoke 在 transcript 写入前和 queued event 已落盘后各注入一次 OSError,实际编译的 App 调用得到 [503, 202],独立回读只有一个 Turn、一条用户消息、一个 queued event 和一次 dispatch。上轮的“HTTP 400 使 App 不重试”问题在这个 head 已修正;不能沿用旧 blocker。
具体改动
关键代码讲解
planChatTurnAcceptance(loopx/control_plane/turn_driver/chat_turn_acceptance.ts:711)区分 prepared、settled、运行和终态,校验原始意图以及 transcript/event 前缀;冲突不会转成新执行。新的 typed owner 由chat.turn.accept真正调用,不是测试专用框架。ChatSessionStore.accept_managed_turn(loopx/chat_store.py:747)在 session 锁内读取完整来源、逐步持久化,并以稳定消息标识修复合法前缀;JSONL 修复只处理不完整尾部,保留完整末条和 Unicode。终态 capsule 的清理不占住下一 Turn,也不重新 dispatch。ChatRuntimeController.submit_turn(loopx/chat_runtime.py:889)先做 home admission,再接受请求、恢复 adapter 和启动唯一 worker;adapter 启动失败仍保留 queued 请求。队列准备失败会真实结算,完成/中断不释放后继 Turn 的 ownership。HTTP 将接受写入 OSError 映射为可重放的 typed 503,隐藏底层 IO 细节。acceptChatTurn(apps/presentation/dashboard/src/data/chat.ts:794)最多两次、原请求体重试;网络/响应体中断、服务不可用以及已接受请求的 resume failure 可恢复,validation 400、冲突 409、home 424 不会盲目重试。新增 HTTP fixture、编译 smoke、CI job 和 Python/TS 回归测试覆盖该链路;manifest 仅调整三处行号,tsconfig 纳入新 owner。
对主干的风险
[P2] 新 smoke 再次绕过统一的 Python 发现机制
位置:smoke 的解释器选择第 87–89 行。它自行选择 LOOPX_PYTHON、POSIX venv 或 bare python3,不消费仓库已经支持的 LOOPX_TEST_PYTHON,也不检查版本。实际给定有效的测试 Python 3.13 和旧的 legacy Python 3.9 时,resolveTestPython 正确选择 3.13,但这个编译 smoke 启动 3.9 并退出,报 acceptance HTTP fixture exited before returning a result。这不是 CI 颜色,也不是推测的兼容问题。
最小修复是直接复用 scripts/test-python.mjs 的 resolveTestPython({repoRoot: repositoryRoot}),删除本地 discovery。现有 guard 在这个 head 仍通过,因为只识别直接启动/赋值和 ?? "python3",漏掉了括号内 ternary;请给 guard 加该形态的反例,并测试 test override 对 legacy override 的优先级,避免下一次又漏过。复验统一 guard 和 npm run smoke:chat-turn-acceptance-retry,包括这个有效 test override 场景。
语义与 CI 对齐
独立验证:相关 Python 234 passed;acceptance planner 与 Python guard 27 passed;真实编译 App/HTTP smoke 在正确解释器下通过;control-plane/dashboard typecheck、mypy、相关 Ruff、diff check 通过。同一真实 store 故障/retry oracle 在基线因 original request unavailable 失败,在 head 恢复为一个 message/event,证明测试对历史缺陷敏感。HTTP validation/conflict/home 的无副作用拒绝也已验证。
按照当前 review 配置没有查询或等待远端 CI。registry-I/O census 在相同依赖准备后的 base/head 都是 5 passed / 1 failed,失败用例相同,直接 validator 回读的八项诊断完整相等;这处既有漂移不作为本 PR 的 blocker。真实上游模型/attached host、完整跨平台和长期并发 soak 未在本次独立验证中运行,不能拿这个 synthetic upstream fixture 宣称它们合格。
我的整体评价
REQUEST_CHANGES。核心恢复链路改善了持续工作和 App 用户体验,原始意图 capsule 的持久兼容有必要;743 行 planner 和 Python IO bridge 必须保持单一规则 owner,后续不应再复制接受状态机。我的 bounded future-facing pass 的具体建议是这次就收回 smoke 的重复 discovery,并补 guard 的漏检,而不是扩展新框架或重新实现整个恢复方案。新的开发验证入口仍有可复现的体验回归,先修这个小而明确的缺口即可复审;不要求这份 PR 修无关主干问题,不建议合并或 bypass。
English verdict: REQUEST_CHANGES - head 8d64afd. The real App/HTTP acceptance recovery now passes, but the new smoke bypasses resolveTestPython and ignores a valid LOOPX_TEST_PYTHON, reproducibly starting Python 3.9. Reuse the existing resolver and cover the ternary/override regression. Independent validation: 234 Python tests, 27 TS tests and compiled HTTP smoke passed with the intended interpreter after revalidating the unchanged-patch rebase; no remote CI was queried.
|
Addressed the Python discovery blocker in signed commit
Reproduced the reported environment locally: Please re-review exact head |
|
Exact-head CI update for
Exact-head run: https://github.com/loopx-project/loopx/actions/runs/36320608605. |
5fcbde6 to
839fcd7
Compare
|
Aligned the PR to
The new exact-head CI run is now in progress. Re-review remains requested from |
huangruiteng
left a comment
There was a problem hiding this comment.
Exact reviewed head: 839fcd76d0d0cdd26f783ba86f2b629f6568fb20; immutable baseline: 473263cbfd7d877e058941c9a770de4c7953d3bd. This is a fresh review of the full current PR, not inherited approval from8d64.
动机
#5064 / App conversation RFC 的 Acceptance、Dispatch、Managed 行要求:同一次已接受请求在落盘或响应中断后,仍能由原 owner 恢复,不让 busy Turn 卡死。独立真实-store oracle 在基线复现了“original request is unavailable”;当前 head 恢复同一 Turn、一条用户消息及一条 queued event。这里只验收 managed acceptance/recovery 切片,不宣布整个异步 Inbox 计划完成。
改动思路
沿用 Session lock 和既有 Turn 存储,把不可从缺失 transcript 反推的原始请求暂存为 prepared intent;TypeScript owner 判断合法持久化前缀与恢复计划,Python 读取事实并执行 IO。App 保持同一身份和请求 body,只对明确可重试故障做最多两次尝试。只加 HTTP retry 无法补齐缺失原始意图;改造整个异步 Inbox 也不是此处必要条件。
具体改动
完整 18文件 +3457/-184 包含 typed planner、Python bridge、store/runtime、App/HTTP、编译 smoke、CI/build 接入及 manifest 的机械行号更新。先前相对8d64的增量仅两个 smoke/guard 文件(+15/-6)。发布前检测到 rebase 到473263cb,新 PR diff 的 stable patch-id 与已审查5fcb完全相同;新 main 的相邻 App 回执改动已读过。当前839fcd再次通过255 Python、完整TS、App typecheck/compiled HTTP和新基线上的store/runtime反例,未把旧 head 的批准迁移过去。
关键代码讲解
planChatTurnAcceptance(TS:711):显式状态、scoped identity、request digest 与完整 transcript/event 前缀决定 accepted/rejected/write plan,本身不执行 host effect。ChatSessionStore.accept_managed_turn(store:747):在同一锁内 prepare → active/session → stable transcript → queued event → retire capsule;原始记录缺失的 legacy 状态继续 fail closed。ChatRuntimeController.submit_turn(runtime:889):先执行既有 home/attached gate,再接入 managed acceptance 和唯一 worker key;resume/interrupt 使用原始 prepared intent,不抢 attached authority。acceptChatTurn(App:794):保留 body/identity,重试 transport/response failure、5xx和明确 resume_failed424;400/409及其他424不重试,取消信号不被吞掉。
独立验证:255 Python passed;完整 control-plane TS 3310 passed / 0 failed / 30 optional skipped。control-plane/dashboard typecheck、配置内 mypy(19文件)、相关 Ruff、diff check、public boundary 和 19项 premerge canary 全部通过。真实 Vite 编译 App → 实际 HTTP server → file store/TS planner 的两个故障窗口得到503→202且各只有一个 accepted Turn/message/event/dispatch admission;provider/worker 在 fixture 中隔离,所以不把它描述成真实模型执行验收。
同一 reviewer harness 在 immutable base/head 比较实际 create_turn 和 submit_turn:合法/冲突/busy 完整诊断、拒绝前后状态、terminal 旧请求与 successor、125条无关消息及顺序变化、managed/attached/foreign/new-session/home-escape/restart-original-owner 均满足既有不变量。恢复 oracle 对旧缺陷失败、对当前 head 通过,避免只靠新正向测试。
对主干的风险
先前阻塞的解释器问题已被真实旧/新 head 验证解决:相同3.13 test override +3.9 legacy override 下,旧8d64确实启动3.9并失败,当前 head 复用中央 resolver 并通过;native guard 对 reviewer 的 parenthesized ternary mutation 实际失败,移除后5项通过。没有宣称正则可覆盖任何未来 AST 写法。
registry-I/O census 的8条完整诊断在 base/head byte-equivalent,不是只比较错误数量,且与变更路径无因果关联;不据此 request changes。另一个可选、未纳入配置的 broad Chat mypy 在基线已有4179条错误,当前4180:该完整诊断比较在9ea/5fcb执行;最终rebase没有改动任何Python/control-plane/tests/config/dependency-lock源闭包,因此这部分诊断证据可复用(配置内检查和相关native suites已在新head重跑)。逐条比较发现新增一条 runtime:1295 的 Event|None narrowing 提示。owns_done_event 布尔别名让 mypy 无法证明 else 分支的 current 非 None,运行时判断本身安全,相关 queue terminal tests 通过。非阻塞 P2:可直接用 if current is None 改写,保留 ownership 行为;这条新提示没有被错误归为 baseline。30项可选 PostgreSQL/NoKV skip 不代表那些 backend 已验证,且此 PR 没有 authority-store refactor。当前策略未查询或等待远端 CI。
我的整体评价
APPROVE。核心 recovery 边界有真实 consumer、历史缺陷敏感性和同范围反例验证;typed decision owner、默认行为变化、domain-neutral contract 和 enforced-vs-guidance 区分一致,没有新增调度/权限/peer authority。Future-facing pass 已应用:删除重复 Python discovery,复用已有 helper并加 mutation/precedence 防回归;上述直接分支 narrowing 是小的非阻塞建议,不需要扩展新框架。live provider、真实 attached host、跨平台 soak 与后续 Inbox 阶段仍未在这里验收。没有执行合并。
English verdict: APPROVE - head 839fcd7. The prior interpreter blocker is fixed and independently regression-tested.255 Python tests,3310 TS tests, compiled real HTTP/file-store recovery and19 canary checks pass. Paired real caller/state/scope counterfactuals preserve existing semantics while historical interruption now recovers. Unchanged census failures do not block this PR; one new optional mypy narrowing warning is a non-blocking P2, not mislabeled baseline. No live model/attached-host qualification or merge authorization is implied.
839fcd7 to
5ac7cc6
Compare
|
Aligned again after main advanced through Direct
The latest main currently has two unrelated baseline failures that I did not fold into this PR:
The prior exact-head approval was correctly invalidated by this rebase. Please review the current exact head |
5ac7cc6 to
4712a19
Compare
|
Aligned again to The five PR commits rebased without conflicts. The completed prior exact-head run
Post-rebase local verification on
The new exact-head CI run is in progress. |
4712a19 to
b817e7e
Compare
|
Correction to my prior CI attribution: the Python shard 4 top-level module budget failure was introduced by this PR, not by main. The new Fixed in DCO-signed commit
I also rebased all six PR commits onto current Post-rebase local evidence:
|
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
b817e7e to
275a2a8
Compare
|
Main advanced again through New exact head: The superseded |
Goal And Delivered Outcome
client_turn_id.main.Scope And Continuation
Validation
97dd19b3f7f871092ed50727b3abaf9d20f2bceaunitpassednpm run test:control-plane: 3175 tests, 3145 passed, 30 skipped, 0 failed on the equivalent patch before a governance-only main rebase; the exact head's 19 acceptance reducer tests also passed.unitpassedstaticpassednpm run typecheck:control-plane, changed-file Ruff, andgit diff --check.real_entrypointpassednpm run smoke:chat-turn-acceptance-retry.integrationpassedscripts/loopx canary premerge --from-git-diff --tier standard --no-progress: 18 selected checks passed with no warnings or manual holds.staticpassedregression_parityfailedexamples/repository-hygiene-smoke.pyreports the missingv1.2.0release-timeline entry on both this revision and an isolatedorigin/maincheckout; this diff does not touch that surface.startingrecovery, completion publication, same-key concurrency, response-body loss, HTTP 424/5xx retry, and 409 non-retry. No UI layout changed, so visual browser evidence is not applicable.See validation disclosure guidance.
Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).none.Signed-off-bytrailer (git commit -s).