Skip to content

fix(lark): keep admissions moving during background stop - #6685

Merged
loopx-agent merged 2 commits into
mainfrom
codex/lark-stop-ingress-20261017
Oct 16, 2026
Merged

loopx-agent merged 2 commits into
mainfrom
codex/lark-stop-ingress-20261017

Conversation

@loopx-agent

@loopx-agent loopx-agent commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Completed-turn background cancellation currently runs inside Lark inbound admission and holds the topic route fence through native inventory and interruption. A later message or duplicate stop can therefore wait even after the original targets have been durably fixed.

Freeze the existing Core request under its source fence, then let the existing reply workers perform native control while holding only that original request fence. Duplicate waiters take their request fence before the topic fence. The transport retains its existing captured recovery format; rollback readers can finish the same request. Active-Turn interruption, fresh authority/source checks, frozen targets and verified once-only replies stay with their existing owners.

Validation at f296119:

  • The shipped listener callback, real File/typed Core, synthetic provider/Codex wire and real child processes exercise a gate inside the native inventory RPC. A later canonical Turn is durably admitted while that RPC is blocked. Native upstream dispatch still respects the adapter mutex; after it releases, the new execution survives and only the frozen old command is terminated. Original stop reply/effect replay stays once-only. Active exact stop, native failures, identity loss, interrupted transport writes, restart and owner-private messages are covered.
  • Same-fixture pinned baseline and deliberate route-lock restoration fail the new admission invariant; head passes. An original installed reader recovers the candidate capture and preserves later work and once-only original delivery.
  • Six-module regression: 257 passed at a93f463. Final commit changes only the test: production and RFC blobs are identical; its complete changed test module passes all 24 cases with the stronger RPC gate. The other 233 tests remain covered by the unchanged prior run.
  • Repository-configured mypy passes. Optional strict checking of the three changed modules has 13 identical normalized baseline errors and no new errors; no global strict pass is claimed. Native premerge, Ruff and bilingual RFC checks are recorded in the exact-head review.

This source slice does not certify installed five-second stop/web timing or the full community release pack. No new queue, consumer, persistent status, user setting or permission is introduced.

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 loopx-agent left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max

Approval conclusion (author-owned PR; GitHub blocks formal self-approval). This is an author-side model review, not a human or independent-person approval. Exact head: f296119190252b9350eafd83512559e28cd36f01. 未发现阻塞项;最强剩余缺口是安装后真实 SDK、飞书及网页时限。

动机

已在飞书话题或本人私聊发出停止指令、随后还要继续工作的用户。

旧版在查询旧后台命令时占住消息入口及话题路由锁,后续消息与重复停止都可能等待;新版先固定原停止目标,再由现有回复工作池处理原生控制,使后续消息先进入持久队列。

已验证的改善是原生清单 RPC 被阻塞期间,后续规范 Turn 仍能持久受理;释放适配器锁后它开始原生执行并存活,原停止效果和答复保持一次。

本次不改原生适配器互斥锁、不新增消费者或队列、不承诺已达到五秒停止/网页时限,也不宣告完整社区准出。

安装后的真实 SDK、飞书回执与整页时延尚未由本 PR 的合成协议回归证明;完整社区用例和三轮关键验收仍由既有交付任务继续承担。

改动思路

停止目标的正确性属于既有 Core 不变量,原生控制属于现有工作池的 IO;把 IO 留在话题路由锁内既非授权所需,也会阻止已经获准的新工作持久受理。

当前 PR 完整交付默认 Lark 入口的“固定原目标后释放受理”及其恢复兼容;安装采用和真实端到端时限沿既有社区验收继续,不新建另一套调度或完成状态。

先保留当前源消息、授权和规范目标的决策,再拆开 IO 阶段。请求锁先于话题路由锁取得,重复停止等待自己的请求时不占住后续消息的路由。原 listener 在 Core 固定原目标后返回;原回复工作池恢复同一请求,没有新建第二个消息消费者。传输层继续使用既有 captured 格式,并以 request_ref、binding_id 和完整 source 对齐 Core;成功回传仍走原 Inbox 的独立来源核验。

下表依据 f296119190252b9350eafd83512559e28cd36f01 的源代码描述锁寿命,不代替执行证据:

触发与阶段 旧版 此版本
旧后台命令清单慢 入站和话题路由等原生 IO 原目标落盘后,回复池执行 IO
重复停止等待 可能占着话题路由锁 只等原请求锁
后续规范请求 可能被旧控制阻塞 已验证在清单 RPC 内阻塞时仍持久受理
原生执行与最终回执 适配器串行,控制后回传 仍串行;控制与来源核验成功后才回传

具体改动

生产代码三处 +92/-42,现有测试文件 +204,两个 RFC 镜像 +14。没有生成文件、私密信息、新 CLI、队列、持久状态或用户权限。测试门设在真实 native RPC 写入边界,适配器互斥锁仍被持有;断言的是已落盘受理。释放后才等待上游执行,核验新执行存活、旧归属子进程退出、无归属子进程保留和原停止回执/效果恰好一次。

关键代码讲解

  1. ChatExternalConversations.admit:接收原请求,按请求锁→路由锁核验当前授权,持久固定目标;离开路由锁后才执行旧后台 IO。精确重放仍核对原内容,未记录原范围的旧请求不借当前工作重建意图。
  2. _finish_background_stop:只读取固定候选的原生存活子集,再调用既有类型化响应投影与确切中断 owner。它不改已完成答案,不扩大候选;原生错误保留原请求供恢复。
  3. LarkPrivateConversations.admit:既有源消息及 App 核验后调用 Core;延后控制时仍持久写 captured,回滚读者无需认识新状态。该参数只表示调度,没有授权意义。
  4. reconcile_request:恢复前核对原 request/binding/source 三元关系,Core 完成后复制既有 response_code 等结果字段;仍 prepared 时保持未确认,缺失或不匹配走旧完整受理路径。最终投递继续重新核验实际来源。
  5. _poll_profile:把现有 callback 绑定为延后旧后台控制,使用现有回复池。活跃 Turn 的确切停止不等历史清单;原生执行本身仍受已有适配器互斥锁保护。

规范依据是 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md,接受的变更前版本 c9c3adf63f9771ef95f1fcca12b11ed7de1f69eb,107–135 行。下面的标识是无编号条款的描述标签:

criterion_id 处置和证据
original-source-and-thread implemented:原 App/消息/root/授权检查及原话题投递不变,相关负面回归通过。
freeze-before-native-inventory implemented:Core 在原生 IO 前固定原候选;最终 RPC 内阻塞、写盘中断和重启回归验证。
unknown-and-closed-original implemented:未知旧目标与已关闭原 Session 保持未确认;既有 legacy/new_session 负例通过。
active-stop-and-once-only-effects implemented:活跃停止在 base/head 均通过;新执行和无归属子进程存活,原停止效果及 terminal ledger 不重放。
separate-installed-and-community-exits deferred:真实安装、SDK/飞书/网页五秒时限、完整社区包及三轮关键验收由既有交付任务继续,本源审查不宣告完成。

RFC 新增段落只说明默认 IO 时序,不把实现新增的描述反过来当作独立验收依据。

对主干的风险

最危险的误判是把“路由锁释放”当作“上游执行并发”。早期测试门在查询 helper 入口前;这不能证明适配器锁持有期间的行为。最终门已移入 thread/backgroundTerminals/list 的实际 RPC 写边界,只要求规范 Turn 先持久受理,之后才断言上游执行并存活。新 Turn 合法进展可能增加一条消息,故重复性 oracle 锁定原停止 tuple、terminal ledger 和 terminate 调用,不再错误要求整个写消息列表永远不变。

相同最终 fixture 在固定 base 为 1 failed/1 passed,在 head 为 2 passed;刻意把路由锁放回原生控制时变为 1 failed/1 passed,新受理超时。最终完整改动测试模块 24 passed;之前六模块 257 passed,最终提交只强化测试,五个运行代码/RFC blob 相同,因此复用其余 233 个未变用例。旧安装版本的真实方法读取候选 captured/Core 日志,另有 1 passed,保留后续工作与原回执一次。

实际默认调用链是 Chat 启动→Lark runtime→原 callback→Core,非只增一条不可达测试路径。原生 inventory/terminate、身份失败、写盘中断和重启均保持原目标与未确认状态;关闭原 Session 仍被原生适配器拒绝重开。没有声称 source fixture 能证明生产 SDK、网络回执和整页时延,也未放宽现有 scope/权限或适配器串行边界。

语义与 CI 对齐

采用 reuse_existing:prepared、target_recorded、captured 与既有类型化 stop/响应投影仍只有各自原 owner。语义扫描的动态来源盲点不被当作安全证明。最终 native premerge 的 6 项直接检查和 11 项选中检查通过,包含双语 RFC、架构 ratchet、语义词汇及公开边界检查;Ruff 通过,仓库配置的 mypy 对 17 个文件成功。可选的三文件 strict 检查仍有 13 条与固定 base 同身份同详情的旧错误,没有宣称全局 strict 通过。依据当前 Goal,未拉取、轮询或等待远端 CI。

我的整体评价

APPROVE,交付判断为 justified_increment。long_horizon = improved:后续持久受理与原请求恢复不再相互阻断;user_experience = improved:用户继续使用原停止及后续消息,无额外导航、重新输入已知信息或确认轮次。原生执行与最终答复仍可能等待适配器和外部服务,五秒时限尚未由此源测试证明。

已有停止正确性、原消息核验和旧 captured 读者属于必要兼容;本次没有增加版本分叉或替代状态 owner。较小的“只移到 worker”方案被路由锁变异实证否定,新队列或绕过适配器锁则增加无依据的状态/并发负担。保持现有 owner 的完整小切口有正收益;完整社区准出仍开放。审查结论绑定上述 exact head,若生产逻辑再变,需要重新核验这条受理、原范围、实际效果与一次回执的轨迹。

English verdict: APPROVE this bounded default-listener admission fix. The real native-RPC boundary proves durable admission, not concurrent upstream dispatch or installed latency. Frozen scope, recovery and once-only original effects remain covered; production adoption and full release acceptance stay separate.

@loopx-agent
loopx-agent merged commit dededb6 into main Oct 16, 2026
23 of 33 checks passed
@loopx-agent
loopx-agent deleted the codex/lark-stop-ingress-20261017 branch October 16, 2026 21:50
@loopx-agent

Copy link
Copy Markdown
Collaborator Author

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · reasoning_effort=max

Post-merge audit — new actionable concurrency finding

评审 head:f296119190252b9350eafd83512559e28cd36f01;不可变 base:c9c3adf63f9771ef95f1fcca12b11ed7de1f69eb。该 head 已合入 dededb6174c3768fed55b67cb1e4ea3f4fbbce52,二者整树相同(tree e1d23db72dddb44b53da7070f4f5690473d35079)。本审计覆盖全部 6 个文件、+310/-42,并给出既有批准未覆盖的“不同消息 ID 的第二条停止”反例;保留原请求重投等已通过证据,不重复等价批准,也不作形式上的撤销或合并操作。

动机

在 Lark 群话题或本人私聊中,用户要求停止已完成答复遗留的后台命令,随后继续补充工作。
原实现让监听入口等到后台命令清单和取消完成才返回;本改动先固定原目标,再由已有回复工作池继续处理,普通后续消息可以先被持久受理。
同一停止请求的重投已不会阻塞普通入站;但另一条新消息形式的 /stop 仍会等待原生控制锁,并再次挡住整个话题的后续入站。
本次只判断已有 Lark/Core 路径的源实现;实际安装、真实飞书和 Codex 服务时延、五秒停止目标以及完整社区准出不在本次已验证范围。

改动思路

复用现有请求日志、类型化 Core 判定和四个回复 worker 是合适的边界;当前还须消除 canonical 候选扫描对原生 adapter 锁的依赖,不能只把第一条停止请求的 IO 移出路由锁。
这个受理修复切口应交付同一话题在后台停止等待期间仍可持久受理普通消息,并保持原目标、失败恢复和一次回复;不新增队列、状态词汇或泛化调度框架。

原路径在同一个话题路由锁内完成目标选择、原生清单读取和取消;新路径先取得请求锁,再短暂取得话题锁完成原目标冻结,释放话题锁后处理原生 IO。默认 listener 通过 partial(..., defer_background_stop=True) 选择分阶段调度。回复工作池恢复相同请求,不创建第二个执行者;原生成功和原来源投递验证完成后才允许确认停止。直接调用者省略参数时仍同步完成。下面的表是当前实际探测结果,不是时延或安装资格。

首条停止的原生清单仍被阻塞时 后续普通消息在释放屏障前持久受理
没有第二个停止请求 是
同一消息 ID 的重复停止 是
不同消息 ID 的第二条 /stop 否;释放原生屏障后才受理
仅诊断性移除 canonical 扫描的 adapter 等待 是;不作为可交付修复认证

具体改动

规范依据是 base c9c3adf63f9771ef95f1fcca12b11ed7de1f69eb 的 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md。以下为该段未编号要求的评审标签:frozen-original-scope、no-retarget-on-replay、exact-active-stop、truthful-result-and-recovery、separate-installed-exit。前四项由当前真实 Core/文件存储与模拟协议、实际子进程的回归支持;安装和线上时延保持单独出口。新增中英文段落对“慢速控制不阻塞入站”的承诺尚有下面的具体缺口,不能把改写规范当成实现证据。

关键代码讲解

  1. ChatExternalConversations.admit() 调换请求锁与话题锁次序,在话题锁内重新解析来源及授权、核验重放内容并冻结候选。相同原请求的等待者先等自己的请求锁,因此不会占住话题锁。_reject_prepared() 统一原有确定拒绝路径。
  2. _finish_background_stop() 从已固定的原 Session/Turn 集读取原生清单,只缩小目标,再复用类型化 collaboration.conversation.request 投影响应;真实取消成功后才写 command_completed。没有把后来的 Turn 加进旧请求。
  3. LarkPrivateConversations.reconcile_request() 对身份相同且已固定目标的 Core 停止请求复用恢复入口,保留旧 transport 的 captured 格式,再继续已有来源核验和投递回执。默认 listener 的唯一调用点在 goal_topic_runtime_service.py,已有四 worker 承担恢复,没有引入队列、用户设置或新状态值。
  4. tests/test_chat_conversation_bindings.py 增加群话题和本人私聊、原生清单/取消失败、transport 写中断、身份不可用、重启、重复请求、后续 Turn 和即时活跃中断的集成场景。第二提交把并发屏障移进真实 adapter RPC 的写入口,这比在 helper 外暂停更有辨别力,但仍只覆盖原请求的重投,漏掉新的停止请求。

原默认同步入口的同输入 base/head 读回完全相同:原 Session、固定目标、完成回执、响应码、一次取消及一次回复均保持。另将候选产生的 prepared/captured 日志交给上述不可变 base 的原 Core/transport 源模块恢复:只停止原目标,后来的原生 Turn 保持运行,未知子进程存活,原停止回复验证且只发送一次。这证明源读取兼容性,不等同于实机降级验收。原生清单等待期间撤销原 binding 的独立探测也保留拒绝:没有迟到的取消或成功回复。

对主干的风险

[P2] 新停止请求仍会把原生 IO 等待带回入站路由锁

第一条停止请求 A 已固定目标,回复 worker 正在 _finish_background_stop() 的 thread/backgroundTerminals/list 内等待,并持有 _session_adapter_lock。此时用户发送另一条消息 ID 的 /stop B:admit() 先取得话题路由锁,随后 _admit_prepared() 的 completed_turn_ids() 又等待同一个 adapter 锁。于是 B 把原生 IO 等待重新带回路由锁,第三条普通消息 C 无法受理。请求锁次序只解决同一 request_ref 的重投,不能解决这个常见的再次停止场景。

独立反例经默认 listener callback、真实 Core/类型化 bridge 和文件存储运行:无第二请求与原请求重投两个控制组都能在屏障释放前受理 C;新 /stop 组在三秒观察窗口内没有 C 的 Core 请求,释放原生屏障后才变为 durably_accepted。这不是生产时延结论,而是由确定屏障显示的锁依赖。只在诊断进程中去掉 canonical 扫描的 adapter 等待、保留其原读语句,该组也能在屏障释放前受理;仓库源码未修改,完整修复的并发安全仍须作者验证。

最小修复:让路由锁内的 canonical 候选冻结不等待原生 adapter IO,复用现有 Core/store 读边界并保留原请求冻结及 effect 前授权检查;补上两个不同消息 ID 的 /stop 与第三条普通消息的并发回归。不要靠全局取消锁、扩大目标或延长超时解决。
复跑应保留现有活跃停止、重放不扩目标、取消失败及重启回执测试,并在 test_chat_conversation_bindings.py 的现有 listener journey 中加入“首条清单阻塞 → 不同消息 ID 的第二条停止 → 普通后续消息”的场景;普通消息必须先持久受理,且不能提前回复停止成功。

语义与 CI 对齐

复用既有 prepared、captured、command_completed 及既有类型化请求判定,没有新增共享词汇;defer_background_stop 是调用方的短生命周期 IO 调度参数,不是用户授权、默认关闭能力或新的持久状态。listener 默认行为确实改变,PR 正文和中英文 RFC 均已披露;Core 直接调用默认值仍为 false。无需为本次修复复制 TypeScript 决策 owner 或做无关迁移。相邻边界的有界重构已体现在取消阶段与拒绝 helper 提取;剩余必须修的是上面的锁耦合。

当前本地证据:七个相关模块 306 passed in 496.39s (0:08:16);独立并发反例 2 通过、1 失败;诊断反事实 1 通过;同步默认 base/head 各通过,撤权与原读取器恢复通过。Ruff 通过;仓库配置的 mypy 通过(17 个源文件,不声称全仓严格类型检查)。先跑语义 advisory,再执行完整语义 smoke,均通过。标准 premerge 的 6 个 direct 检查、11 个 catalog/risk/public-boundary 检查都通过;总体仍为 quality_receipt_missing,这是评审工作区没有当前质量回执,未伪造作者回执或宣称整门通过。本轮依当前策略没有获取、轮询或等待远程 CI。原生 RPC 与 Lark 是可控模拟协议,取消对象为真实测试子进程,未对真实用户会话发消息或终止工作。

我的整体评价

REQUEST_CHANGES。这个方向能在已有 owner 中交付真实增量:原停止请求的慢 IO 和重投已与普通入站分开,目标和结果回读也保持清楚。长期连续工作的已验证范围包括原请求重放、失败后恢复、后续工作保留及源读取器兼容;不把这些局部证据当成长期运行或真实安装净收益。用户体验目标仍未完全成立:另一条新停止消息会重新堵住入站,用户仍需等待底层控制返回才能继续。

复用现有请求日志、类型化 Core 判定和四个回复 worker 是合适的边界;当前还须消除 canonical 候选扫描对原生 adapter 锁的依赖,不能只把第一条停止请求的 IO 移出路由锁。
这个受理修复切口应交付同一话题在后台停止等待期间仍可持久受理普通消息,并保持原目标、失败恢复和一次回复;不新增队列、状态词汇或泛化调度框架。
请沿既有源修复 owner,通过后续正常 PR 补齐 canonical 候选冻结与原生 IO 的锁边界和上述三消息回归,再按新 exact head 复核;不需要另建重复任务、队列或批准步骤。这里的 REQUEST_CHANGES 是对已合入代码的修复结论;当前缺口是这个可复现的路径,不是远程 CI 状态或必须完成整个父路线图。

English verdict: REQUEST_CHANGES - head f296119. A distinct second /stop waits for the native adapter lock while holding the shared route fence, blocking a later ordinary message. Real Core/File and child-process probes distinguish duplicate replay from a new request; local positive, recovery and compatibility evidence is retained. The bounded canonical-read/IO lock boundary and a three-message regression need repair.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant