Repository navigation
Avoid reparsing completed Chat Turns during App and Lark admission - #8316
loopx-agent wants to merge 3 commits into
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@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
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
[P2] 匹配记录的第二次读取失败会重复准入同一请求。 同一 client_turn_id 重试时,File Chat 的发现读取已经匹配原记录。只让随后重新打开该文件的一次 read_text 返回 EIO,已排队和已完成两种状态都从原来复用一个 Turn 变为创建第二个 Turn,并多写一条用户消息。
动机 / Motivation
使用稳定 client_turn_id 重试的 App 和 Lark 私聊队列依赖已接受的请求只创建一个 Turn。 用真实 File ChatStore 对照长历史排队、健康重放和匹配记录的单次 EIO。 热排队减少历史正文读取;正匹配后的读取失败会重复准入。 核对了 3 条变更路径和 File Chat 的调用链。运行时配置与 UI 没有变动。
改动思路 / Approach
复用同一终态观察供重放查找和容量使用;正文、附件、结果仍由真实记录和原重放校验读取。 复用原终态指纹、状态和准入锁,范围合适。首次完整读取已经完成时,第二次打开既增加 I/O,也给请求重放引入失败分支。 问题限定在 File Chat 已知匹配的重试。缓存收益和故障安全分别核验。
具体改动 / Changes
基线为 e58ba30bc949c29ec90c570a8362fb1e7ec9f3be。RFC 定义 client_turn_id、response-loss 重放、不同请求正文的拒绝及现有 File 观察边界。 基线规范
- 长历史排队。 在相同的 320 条、每条 12 KiB 的已完成历史中,新消息每次排队读取的历史正文从冷路径 640 次降到 320 次,热路径五次均从 320 次降到 0 次。热路径中位数 43.29 → 25.07 ms。健康重试返回原 Turn 和最新响应,同 ID 不同正文仍被拒绝。
- 重试身份。 同一 client_turn_id 重试时,File Chat 的发现读取已经匹配原记录。只让随后重新打开该文件的一次 read_text 返回 EIO,已排队和已完成两种状态都从原来复用一个 Turn 变为创建第二个 Turn,并多写一条用户消息。
关键代码讲解
旧版直接使用第一次成功读到的整条记录。新版在匹配后总是再次读取,_read_json 将这一轮 EIO 转成空对象,turn_for_client 返回 None,create_queued_turn 于是走分配新 Turn 和写 transcript 的分支。 匹配后重读 · 读错误处理 · 队列准入
对主干的风险 / Risks
前后对照保留了优化收益。已确认命中的请求在第二次 I/O 失败时仍需守住去重,尤其是用户重试已完成消息。
508 项 File Chat、原生 Steward 和 Lark 私聊相关测试通过,另完成长历史和四组同条件重放对照。 Quick premerge 的 6 项直接检查和 2 项选中检查通过。 DCO trailer 已核验。主干在评审期间合入 #8317,变动只涉及研究页面,与本 PR 无重叠,合并树检查通过。 基线第一次读成功,未触发第二次 I/O;当前 head 的第二次 I/O 才触发注入。两次试验都使用实际落盘记录和队列写入。 远端 CI 未取样。
语义与 CI 对齐
沿用现有客户端请求标识和终态集合。验收用例需覆盖已匹配后的暂时读取故障,并断言原请求的存储身份。
我的整体评价 / Verdict
直接使用本次已完整读取的匹配记录。缓存只交付终态元数据时,正匹配后的完整读取失败应返回可重试的不可用结果,阻止重复准入。补齐冷、热、queued 和 completed 的故障用例,并核对健康重试及新请求的长历史读取数。 当前证据限于隔离的 File Core 入口和本地检查。安装后的端到端行为和生产锁尾延迟仍需另行验证。
English verdict: REQUEST_CHANGES. A transient second read of an already-matched request admits a duplicate Turn for both queued and completed records. Keep the long-history optimization and preserve replay identity under I/O failure.
| continue | ||
| # Replay validation needs the complete current record, including | ||
| # the original request body and the latest persisted result. | ||
| payload = _read_json(path) |
There was a problem hiding this comment.
[P2] Preserve a matched request when the full read fails
同一 client_turn_id 重试时,File Chat 的发现读取已经匹配原记录。只让随后重新打开该文件的一次 read_text 返回 EIO,已排队和已完成两种状态都从原来复用一个 Turn 变为创建第二个 Turn,并多写一条用户消息。 旧版直接使用第一次成功读到的整条记录。新版在匹配后总是再次读取,_read_json 将这一轮 EIO 转成空对象,turn_for_client 返回 None,create_queued_turn 于是走分配新 Turn 和写 transcript 的分支。 直接使用本次已完整读取的匹配记录。缓存只交付终态元数据时,正匹配后的完整读取失败应返回可重试的不可用结果,阻止重复准入。补齐冷、热、queued 和 completed 的故障用例,并核对健康重试及新请求的长历史读取数。
…rk-queue-terminal-observation-20261108 Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@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
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
[P2] 托管准入会在瞬时读故障后重新派发已完成请求。
动机 / Motivation
App 和 Lark 通过稳定的 client_turn_id 重试已接受的输入。重复创建会增加 transcript 并可重复触发上游工作。 用真实 File store 和普通项目 App HTTP 对照长历史排队、托管准入及已匹配后的单次读取故障。 缓存减少历史正文读取。新 head 的排队和托管入口在单次读故障后均可能重复执行。 核对了从当前 main 到新 head 的全部三条路径,也核对了上次评审之后增加的托管分支。改动落在 HTTP 背后的 File 准入。
改动思路 / Approach
排队、容量和托管使用同一 File 观察,原记录仍提供请求正文、附件和响应。控制面负责托管接受与重放决定。 共用已有终态观察器可以缩短准入锁内的历史读取。缓存只存指纹和状态,第二次读取的失败需要保留已确认的匹配身份。 范围合适。相邻整理应收敛两处正匹配后的读取边界,沿用当前 planner 和去重契约。
具体改动 / Changes
基线为 8a4d4f9dab17b01d6fe3546936155b4d718fc20d。RFC 将 client_turn_id 和 Turn 身份作为稳定重试、响应丢失恢复和不同请求正文校验的契约。 基线规范
- 长历史准入。 同一组 320 条、每条 12 KiB 的终态历史中,五次热准入的历史正文读取在排队和托管入口均从 320 次降为 0 次。冷排队 640→320 次,冷托管保持 320 次。健康重试复用原 Turn 并读回更新后的响应,同 ID 改正文继续被拒绝。
- 排队重放。 此前的排队问题仍可复现。隔离 File store 对 queued 和 completed 同 ID 重试均写出第二条 Turn,App HTTP 排队路径也再次派发了已完成请求。
- 托管重放。 通过真实 App HTTP 重试一个已完成的请求,在准入内第二次读取其已匹配记录时注入一次 EIO。基线的排队请求复用原 Turn,托管请求返回 HTTP 400 并能在下一次健康重试复用原 Turn。新 head 的两个入口均返回 HTTP 202 和新 Turn,同 ID 记录 1→2、新增一条用户消息、上游 turn/start 1→2。
关键代码讲解
旧托管入口先把匹配完整记录交给 planner,第二次读失败发生在返回原记录时。新托管入口把这次读取移到 planner 之前,空对象被 continue,matching_turn 留空,已空闲 Session 遂准入新 Turn。排队路径同样把匹配后的空读当作缺失。 托管分支 · 已复现的排队发现
对主干的风险 / Risks
托管 Session 仍有活动 Turn 时,同一故障在两端都只保留一条记录。原 Turn 已完成并释放 Session 后,候选会重新准入。
相关测试基线 770 项、当前 head 772 项通过。另外运行了实际 File 和 HTTP 的故障对照。 Quick premerge 的 6 项直接检查和 2 项选中检查通过。 DCO 和当前主干合并树检查通过。 12 项 native Steward Turn Host 用例在两边报同样的失败,包括 route 拒绝、缺少 status 和验证未提交。受影响的 File Chat HTTP 准入已独立对照。 远端 CI 未取样。
语义与 CI 对齐
沿用原请求身份和终态集合。建议把单次匹配后读故障纳入现有 File 准入回归,并断言上游派发次数。
我的整体评价 / Verdict
让冷路径直接保留本次匹配时已经读取的完整记录。热缓存命中后需要读取完整记录,若暂时读不到,应沿现有可重试错误路径退出准入。用相同故障覆盖排队和托管路径,断言单一 Turn、原 transcript 和派发次数。 证据来自隔离的实际 File Core 和 App HTTP,模型由本地协议替身提供。Lark 相关用例覆盖路由和持久化,线上 Feishu、Windows 和尾延迟未测。
English verdict: REQUEST_CHANGES. A transient read failure after matching a completed request now creates and dispatches a duplicate through both App HTTP paths. Preserve replay identity while retaining the measured warm-read savings.
| # The planner needs live prepared Turns and the full | ||
| # current request when its client ID matches. | ||
| if existing is None and turn.get("client_turn_id") == client_id: | ||
| turn = _read_json(path) |
There was a problem hiding this comment.
[P2] Preserve a matched managed replay before planning admission
通过真实 App HTTP 重试一个已完成的请求,在准入内第二次读取其已匹配记录时注入一次 EIO。基线的排队请求复用原 Turn,托管请求返回 HTTP 400 并能在下一次健康重试复用原 Turn。新 head 的两个入口均返回 HTTP 202 和新 Turn,同 ID 记录 1→2、新增一条用户消息、上游 turn/start 1→2。 旧托管入口先把匹配完整记录交给 planner,第二次读失败发生在返回原记录时。新托管入口把这次读取移到 planner 之前,空对象被 continue,matching_turn 留空,已空闲 Session 遂准入新 Turn。排队路径同样把匹配后的空读当作缺失。 让冷路径直接保留本次匹配时已经读取的完整记录。热缓存命中后需要读取完整记录,若暂时读不到,应沿现有可重试错误路径退出准入。用相同故障覆盖排队和托管路径,断言单一 Turn、原 transcript 和派发次数。
Goal And Delivered Outcome
Managed App submission and queued App/Lark admission scanned completed Turn bodies while holding the Session admission fence. Both use the same file observation of unchanged terminal Turns. A matching client ID still resolves the full current request for the existing replay checks. The managed planner still receives current prepared Turns. Bound App and Lark inputs use the same queue.
Target:
main. Tested against8a4d4f9dab17b01d6fe3546936155b4d718fc20d.Author Declaration
ChatSessionStore.accept_managed_turn,ChatIngressStore._turn_discovery_rowsChatIngressStore.turn_for_clientScope And Continuation
This change covers managed and queued admission in the file-backed store. Review and production adoption remain separate stages. The previous Windows run hit the shared
loopx_turn_planoutput-growth check (11,404 against 9,300). The refreshed head still needs hosted verification.Validation
33659aeaf5288a00a1c877b1b9eec9f9606426e9. Local validation finished with synthetic and public repository fixtures.The paired arms alternate in one process and swap the exact prior method. For App, that method comes from the previous queued fix integrated with the tested base. Cold means an empty discovery observer. The first cold batch measured App 450.4 → 533.2 ms and Lark 397.2 → 491.1 ms. The expanded cold batch measured App 521.5 → 518.2 ms and Lark 591.5 → 512.8 ms. Cold wall times varied between batches. Production tail latency and Windows CI remain separate.
Frontend / Visual Evidence
Type of Change
LoopX Area
Shared-authority RFC fixture impact
N/A. File Chat admission only.
Boundary Checklist