Repository navigation
fix(chat): continue explicitly unfinished project work once - #7399
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
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
使用普通私有项目助手、已授权其完成原工作区任务的用户,会遇到模型结束一轮回答但仍留下未完成工作的情况。
例如助手已保留第一阶段产物,却因临时来源故障返回部分结果;旧版不会继续这个已结束的模型轮次,新版可在独立核验后沿原会话完成剩余一步。
一次性原生模型协议与消息提供方夹具中,原产物只写一次,剩余产物随后补齐,旧回复不变,新结果送回原来源并能经 App HTTP 接口读回。
本次不回放旧的无结构化标记部分答复,不代替接收 Agent 执行业务,也不扩大工作区权限或建立另一套消息服务。
真实模型能否稳定生成这份报告、自然生产消息的续办送达及整个会话 SLA,仍需安装后沿原 Bot 独立验收。
改动思路
保留现有 Core 决策、外部会话 journal 和原 Session 队列的职责;新增报告表达不可从模型终态推导的剩余意图,原生 IO 只提供观察,不产生授权。
本 PR 交付显式未完报告到一次原来源续办和独立结果读回的完整路径;历史无标记请求、真实模型采用和部署验收留在现有 Bot 维护职责内。
不修改既有“失败且无执行效果”的路由恢复,也不把中断后的只读检查升级成写入。原来源 scanner 仍是唯一消费者,Core 只给同一请求预留一个 work-1;原生适配器证明观察,原队列负责实际执行,原结果阶段负责送达。正常路径复用已知授权,用户无需重复输入或额外确认;60 秒等待与一次上限限制成本,仍未完成时如实保留。
| 原请求状态 | 本次行为 |
|---|---|
| 完成回答、无显式剩余报告 | 保留原结果,不新建续办 |
| 显式剩余报告且来源、grant、完整原生库存一致 | 原队列最多执行一次剩余工作 |
| 发现后续输入、停止、未知效果或撤销授权 | 保留记录并停止该续办 |
| 续办失败或仍未完成 | 保留未闭状态,不落入另一次路由/额度重试 |
具体改动
评审依据是修改前的 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md,固定版本 cdbdc14ea3963e6f6aec4d574a1b88baff965132,而不是用本 PR 新写的规则证明自己。该规范没有逐段编号,下列 criterion_id 是对原有段落的定位标签:
- source-faithful-admission:原 App、本人、消息和 grant 的复核仍由同一受保护入口执行;来源修改、撤销、停止都不继续。
- stable-native-inventory:适配器分页读全原生轮次及 item,核对最新、稳定、效果终态与后台工作;派发时再读,排队的新输入优先。
- immutable-delivery-and-restart:预留先于 enqueue,丢失确认采用确定性原 client;旧 Turn 字节和旧已验证回复不变,新结果使用独立阶段。
- qualification-boundary:测试使用一次性模型/provider,明确不把夹具通过当真实模型、历史请求或完整 SLA 已完成。
上述四项均在本 head 实现并有对应正反例。原规范关于已完成回答不触发“路由故障恢复”的约束继续有效;显式剩余工作是这里另行说明的有界分支,旧自然语言答复不被重新解释。
关键代码讲解
normalize_agent_response只保留精确三字段、文本长度受限、effects_settled 为真的报告;已有 gate、受保护操作、handoff 或 proposals 时拒绝该意图,并复用现有路径脱敏。正常回答不新增必填字段。真实_turn_prompt在模型输入中说明生产条件及禁止把报告当权限,模型实际采用率仍未声称验证。completed_work_readback读取完整最新 Turn 与 item 两次,要求一个原用户输入、一个最终回答、效果 item 全部结束、没有后台任务;未知 item、后续轮次、失败/拒绝/活动效果均不提供证明。它只有控制读取,没有创建新原生会话或模型执行。planUnfinishedWork复用原请求身份、来源和队列规则,检查 60 秒、空闲状态与匹配的独立证明;重启仅采用精确已入队请求。已用过的 work_attempt 不能再落入 routing/quota 分支,防止失败后无界续办。prepare_continuation_input在实际派发前重读库存并检查新排队输入。只对受保护的预留附加“保留既有产物、只做剩余工作”的模型输入,不改保存的原消息。普通项目未配置外部绑定时,运行时保持既有路径。
差异共 198 行生产新增、7 行删除;281 行验证、54 行双语 RFC。最大新增机制均留在既有 owner,没有新 daemon、CLI、业务执行副本或第二状态源。检查过相同源对象的失败恢复、只读恢复、presentation_phase 和 Session 队列,选择扩展其清楚的语义分支,而非重写它们。
对主干的风险
最强反例是“预留已持久化,但随后来了新指令或原生历史变化”。公开入口用例在 crash-after-enqueue 后引入这两种状态,证明旧续办没有启动模型,新指令得到处理,第一阶段效果没有重做。来源被改、grant 撤销、停止、未知/活动库存、仍未完成和两处崩溃边界也均保留原始结果。原生工具 completed 不是第三方业务幂等性的证明,因此报告必须要求已知结果,模型必须先检查产物,不能把该路径宣传为所有外部效果 exactly-once。
最终 head 的 51 项定向 Python、相同基线/新版的 11 项普通项目会话、标准 premerge 的 6 项直接检查及 11 项选定检查、lint 和类型检查通过。相同原来源新用例在基线确实因报告缺失失败,在 head 通过。较早的 307 项 Chat/project、574 项原生/路由/管家回归及 5520 项 TypeScript 通过记录保留;TypeScript 的 75 个既有 skip 不冒充覆盖。实际 native/Core/store/queue/transport/HTTP 路径运行,模型和 provider 是合成边界,未声称真实宿主崩溃或自然消息已验证。未查询、轮询或等待 CI。
语义与 CI 对齐
这是已有回复与恢复契约的可选字段扩展,非新的权限、Goal 或调度义务。旧字段、省略字段、原错误处理和持久记录继续可读;unfinished_work 不能从 prose 推导。报告生产指导是有意的默认提示变化,已经公开说明,不能错误宣称它被某个 default-off 开关完全隔离。语义 drift 检查通过,但有限语法扫描没有发现新 vocabulary carrier 也不等于没有语义变化。初次体积检查真实失败已保留;把派发准备移到原 owner 后同一检查通过,没有提高 ceiling、删断言或放松阈值。
我的整体评价
结论为 APPROVE,交付判断为 justified_increment。long_horizon 与 user_experience 在已验证的原来源路径上 improved:已授权意图不需要用户重述,剩余工作有一次有界机会,失败和新输入仍保持可见与优先级。保留现有 Core 决策、外部会话 journal 和原 Session 队列的职责;新增报告表达不可从模型终态推导的剩余意图,原生 IO 只提供观察,不产生授权。 本 PR 交付显式未完报告到一次原来源续办和独立结果读回的完整路径;历史无标记请求、真实模型采用和部署验收留在现有 Bot 维护职责内。
未修改渲染组件或布局;已检查 App 会话 HTTP 读回和原来源回复路径,因此本次无需新增页面截图。 未验证真实模型报告采用率、自然生产消息续办及安装后运行表现。
这个边界可独立评审和回滚,兼容保留来自真实持久历史需要;不增加另一层兼容 decoder。安装采用与自然生产验收仍独立推进,未闭业务继续归原 Bot/接收者,评审通过不等于该业务已完成或已获得新的合并权限。审查的是 bacb4d1355e702462e8645bf0fe533c8624e1eaa;发布前再次读回相同远端 head。
English verdict: APPROVE bacb4d1. The existing source worker gains one bounded, independently checked unfinished-work continuation without replaying earlier effects or overriding newer input. Current-head positive/negative journeys, baseline parity and standard premerge pass. Synthetic provider/model coverage does not certify live-model adoption or historical business completion.
A completed model Turn can still leave an original private-project request unfinished. The reply worker currently has no typed way to distinguish that case from completed business work.
Add an optional bounded
unfinished_workreport and one continuation through the existing original Session queue. Core requires independent stable full-native readback, the unchanged original source and workspace grant, an idle Session and a 60-second backoff. Dispatch checks freshness again, and newer input takes precedence. Crash recovery adopts the same reserved Turn; a failed or still-incomplete continuation cannot fall through to another business/routing/quota retry.Earlier Turns and partial replies remain immutable. Legacy prose, unknown effects, stops, gates, delegated work, groups, stewards, attached hosts and attachment-bearing requests do not gain continuation eligibility. No additional message consumer or wider grant is introduced.
Validation: 51 focused Python cases at the final head, 307 Chat/project regressions before the behavior-preserving owner extraction, 574 existing native/routing/steward regressions, and the full TypeScript control-plane suite (5,520 passed; 75 skipped). Lint, type checks, bilingual RFC checks and the standard premerge gate (6 direct checks plus 11 selected checks) passed. Native/model/provider journeys use disposable synthetic fixtures, including original-source delivery and real HTTP App readback; they do not prove live-model behavior or completion of historical requests.
The initial premerge run correctly rejected runtime-module growth. Moving dispatch preparation into the existing external-conversation owner fixes that limit without raising its baseline. Installed activation and a natural live continuation remain separate acceptance steps.