Repository navigation
feat(owner-attention): notify owners of terminal Todo failures - #7860
Conversation
Todos carrying a terminal decision outcome (reject/cancel) become an owner_attention subject keyed goal/todo/terminal using the latest outcome, with terminal_outcome_count in the digest so a newly appended outcome starts a new generation instead of being hidden. Unsent notices are superseded when the Todo resolves or the outcome clears; user-initiated pending queries keep reading live state independent of the notice journal. Disabled attention keeps its existing behavior exactly. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Signed-off-by: hhyykk <4408344+hhyykk@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Signed-off-by: hhyykk <4408344+hhyykk@users.noreply.github.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be787591f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| failure = {"todo_id": todo["todo_id"], "text": todo["text"], "outcome": latest["outcome"], | ||
| "reason": latest.get("reason", todo.get("reason", "")), | ||
| "terminal_outcome_count": len(outcomes)} |
There was a problem hiding this comment.
Preserve outcome identities when detecting terminal changes
When a blocked Todo already has terminal outcomes for scopes A and B, a subsequent decision for A can be silently missed. planUserCompletion (loopx/control_plane/todos/user_completion.ts:83–91) records no timestamp and replaces an existing scope's outcome in place, so changing A from reject to cancel leaves B last and the count unchanged. This projection consequently produces identical notice terms, and the planner creates no new generation despite the new cancellation. Reproducing this with the native completion and notification planners leaves the generation at 1. Preserve the typed outcome identities and changes in the projected terms/digest rather than relying only on the last entry and count.
AGENTS.md reference: AGENTS.md:L495-L498
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in c031e44. The owner-attention notice terms now carry every terminal outcome's typed identity (decision scope, outcome, source Todo), order-stable in both the Python projection and the TypeScript planner. An in-place replacement such as A: reject -> cancel now advances the generation even when the latest entry and count are unchanged. An identical identity set keeps the same digest, and outcome prose stays out of the terms.
Regressions: test_terminal_scope_replacement_changes_generation_without_changing_latest_or_count (Python) and native in-place terminal scope replacement advances generation and identical identities stay stable (TypeScript, through planUserCompletion). Validation at c031e44: owner/goal attention TS tests pass, 42 Python tests pass, ruff clean, loopx canary premerge --from-git-diff --git-diff-base origin/main passed.
loopx-agent
left a comment
There was a problem hiding this comment.
REQUEST_CHANGES — be787591f081d4967852c3f60d5d0c522aeb4636
[P2] Preserve all terminal decision identities in the notification digest — 独立验证了现有讨论指出的遗漏,避免再发一条相同 inline。真实 complete_goal_todo 先写 A(reject)、B(reject),再把 A 原位替换成 cancel;canonical 列表是 [A(cancel), B(reject)],无 recorded_at。_goal_failures 仍只投影 B 和 count=2,planOwnerAttention 看到相同 digest,第三次不生成通知。
在隔离 File 和 SQLite 上均经实际 completion API、exact source read、native planner 和原 outbound worker 复现:发送/验证次数 [1,1,0]、通知 generation [1,2,2],旧 delivered receipt 哈希不变。当前 A 的取消是实际新决定,应该产生下一代通知。最小修复:Include the full canonical scope/source/outcome identities in the typed notification terms/digest; preserve existing outbox deduplication, receipt retention and activation. Add an actual in-place completion regression on File and SQLite. 位置:loopx/extensions/lark/owner_attention.py:196–198;不是要求新增监控、开关或一套并行生命周期。
Reviewer: model_agent — gpt-6.1-sol / OpenAI; runtime_reported; reasoning_effort=max.
动机 / Motivation
开启既有所有者通知的用户,需要获知 Todo 被拒绝或取消,以及随后发生的终止决定变化。
原先仅通知待处理任务;提案增加失败通知,但 A、B 两项拒绝后将 A 改为取消,用户收不到这次变化。
首次失败和新增第二项会通知;真实 File、SQLite 完成流程中,第三次原位取消没有通知,代数停在 2,尚未完整交付终态更新。
本次不要求新增开关、监控任务、模型或完整父级 roadmap;仅要求既有 opt-in worker 正确通知当前终态变化。
原请求和已存在的 scoped authorization 足够,不新增准备、选择或确认轮次。仍保留必要的原来源与激活边界;无需用户重复输入系统已有信息。
改动思路 / Approach
复用既有 TypeScript 通知规划与 Python provider IO 合理;最小修复是在原 typed owner 中保留完整终态身份,无需新增通知开关或监控 owner。
当前 PR 仅扩展既有 opt-in 失败通知;真实当前终止变化、去重、恢复和原回执需要一起交付。
实际路径是 Existing configured OwnerAttentionService.reconcile background worker → Existing TypeScript planOwnerAttention owns digest/generation/outbox; Python source adapter currently collapses terminal terms → Existing delivered/superseded outbox receipts and original owner conversation。这条路径的结果必须能被用户后续读取或获知,helper 成功或一张 receipt 不能独自证明交付。采用当前 repository experience 对真实调用、有用结果和既有 owner 的要求,未继承旧审查结论。
具体改动 / Changes
全部 6 文件 +229/-4 已阅读。关键符号:_goal_failures, planOwnerAttention, OwnerAttentionService.reconcile。Complete authorized Goal/Todo list plus exact native reads and canonical decision_scope_outcomes;Current terminal subject gains a generation and existing outbound worker sends/verifies original private route;Existing unavailable-source, unknown-effect and provider verification boundaries; no new monitor or scheduler。
先读已接受的 docs/product/periodic-reports.md,spec_revision 111519af76e56141c56eb7a27443da59def775b9;criterion_id Owner Todo notifications during a source outage:沿用既有 opt-in、原 private owner route、完整当前源、first-enable baseline、pending/unknown 和 delivered receipt 边界。本轮覆盖这些原规则并记录为 implemented。新增 terminal 功能自身仍因上述原位更新遗漏未满足请求;没有拿本 PR 新增文档段落反过来认证实现。
复用与简化:既有 typed owner、源读取和 consumer 足够;保留原始身份、授权与回执,不新增持久注释、另一套 provider policy 或 Python 决策真源。修复应留在上述局部 owner;reviewer 没有提交任何产品修改。
对主干的风险 / Risks
PASS:28 个完整 native attention 测试;41 个完整 Python owner-attention case;200 组不可变 base/head 普通/disabled plan byte parity;typecheck、Ruff、6 direct +11 selected premerge checks,以及六文件 public scan(0 errors/warnings)。FAIL:真实 File 与 SQLite 的原位 terminal completion 反例,共两个失败;新通知没有发出,不能用前两次成功代替第三次验收。
终态是新功能,基线原本没有失败通知;不把此遗漏描述为基线已有功能退化。普通通知与 disabled 路径已做独立 base/head 对照,旧回执不变也已实际检验。
Actual add_goal_todo/complete_goal_todo → exact source read → planOwnerAttention → outbound provider/verification: sends and verifies [1,1,0], generations [1,2,2] on File and SQLite; third change must notify. 预期与观察独立于新 helper 的输出定义;缺失通知和 HTTP 400 均是实际公共路径断言失败。feature-off/非激活行为、完整源不可用、done/superseded 与保留回执分别核对,没有用“字段仍在”代替行为对照。未查询、读取或等待远端 CI。没有 authority-store 改动,未把无关 PostgreSQL、真实账号或安装版运行作为通过事实。
本轮 exact-scope CQR 以未解决 correctness blocker 和失败 required regression 记录为 fail;这与正向检查通过并存,不是 merge readiness。修复后应验证最终准确 head,避免仅重跑上一个反例。
我的整体评价 / Verdict
有价值且有界的方案,但当前结果仍不足以交付。long_horizon 与 user_experience 均为 not_yet_proven:The authoritative A cancellation is lost from the projected terms: last B/count2 stay identical, generation remains2 and the real outbound worker sends no third notification. 最小修复如首条 finding;不要求扩大到父级 roadmap、另建界面或权限流程。
检查 owner_attention.ts 的新纯文本通知及 synthetic provider 正文/回执;未检查实际 Lark 客户端的完整会话呈现,因此不宣称视觉可读性验收。
真实 Lark 客户端视觉、真实账户、已安装版本和模型采用未获本次资格;本次不以 synthetic provider 读回替代这些验收。
复用既有 TypeScript 通知规划与 Python provider IO 合理;最小修复是在原 typed owner 中保留完整终态身份,无需新增通知开关或监控 owner。 当前 PR 仅扩展既有 opt-in 失败通知;真实当前终止变化、去重、恢复和原回执需要一起交付。 本审查只针对该准确 head;未批准、合并或宣称已安装、真实用户采用与持续完整成本验收。
English verdict: REQUEST_CHANGES — head be787591f081d4967852c3f60d5d0c522aeb4636 misses an actual in-place terminal decision update on both File and SQLite. Existing positive checks pass; the independently reproduced blocker remains unresolved. No merge qualification.
Include each terminal outcome's typed identity (decision scope, outcome, source Todo) in the owner-attention notice terms, order-stable, so an in-place scope replacement such as reject -> cancel advances the notice generation even when the latest entry and count are unchanged. Identical identity sets keep the same digest; outcome prose stays out of the terms. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Signed-off-by: hhyykk <4408344+hhyykk@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
动机 / Motivation
已开启现有所有者通知、等待待办终态更新的用户。A、B 两个决定都被拒绝后,A 改为取消;原实现漏掉第三次通知,修复后同一待办能再通知一次且不改旧回执。
当前 File、SQLite 的真实 completion→读取→规划→出站工作器均得到发送/核验 [1,1,1]、generation [1,2,3],此前精确反例已关闭。本次不验收真实 Lark 账号/客户端、安装后采用、长期成本收益,也不授予执行、消息或合并权限。
这次要交付的是所有者获知实际终态变化,避免只能反复打开待办确认。不是以新字段、账本或测试数量证明完成;我重新核验了原 completion 生产者到原通知工作器的整条路径,也重新判断了当前完整 PR。
改动思路 / Approach
保持现有 TypeScript 决策 owner、Python 来源/投递 IO 与原出站工作器,完整身份作为派生数据进入摘要即可;不需要新开关、监控或并行生命周期。交付当前终态通知及中断/重放/缺失源边界;不验收真实账号、客户端视觉或安装后采用。
来源读取仍取得完整、精确的 canonical Todo;TypeScript 规划器仍是 subject、摘要、generation 和 outbox 的单一决策 owner,Python 只负责来源与原 provider IO。修复使每项 decision_scope/outcome/source_todo_id 参与派生摘要,按 canonical 摘要排序忽略列表顺序;原始 scope 不被拼进面向所有者的正文,也不成为新的授权。
新增的是既有开关开启后的 terminal 通知范围:普通 owner-action subject 与 <goal>/<todo>/terminal 分开。机器开关默认和原所有者/Goal grant 不变;首次启用仍只基线化历史,关闭时不进入新来源工作,不增加设置、scheduler 或消息 consumer。完成、被替代、离开 blocked、清除终态会撤回未发送通知,已送达和未知 effect 仍留原回执 owner 处理。
具体改动 / Changes
完整范围 111519af76e56141c56eb7a27443da59def775b9 → c031e44a1aa52ab28be221871c3503bc372d0b66:6 文件,+288/-4。另单独核验上轮 be787591f081d4967852c3f60d5d0c522aeb4636 → 当前 head 的四文件修复;旧失败记录保留,未沿用 Approval。此前 P2 讨论 和本人旧评审提出的 A 原位替换遗漏,已经用相同独立探针重新执行。
规范依据为修改前的 docs/product/periodic-reports.md,revision 111519af76e56141c56eb7a27443da59def775b9,criterion Owner Todo notifications during a source outage:完整授权来源、不可读不等于空、原 owner route、首次启用基线及 frozen unknown/delivered 回执边界。本轮记录为 implemented;新 terminal 需求同时按自包含请求和实际 native producer 验证,没有拿本 PR 新增的文档反向认证实现。
关键代码讲解
_goal_failures从 blocked 列表做 exact read,排除 done/superseded,保留原最新项和计数规则,再投影全部 typed identities。没有依靠正文子串分类终态。planOwnerAttention把这些 identities 规范化并纳入原摘要。因此 A 从 reject 改 cancel,即使最后的 B 和 count=2 不变,也产生下一代;同一身份集合或重排保持 fixed point。reconcile仍在 provider write 前冻结原 intent,重新核对来源/绑定,随后独立 readback。不会因通知、回执存在或状态说明而自动执行 Todo。
goal_attention.test.ts 还验证 pending-answer projection 不因首次抑制或来源缺失丢掉真实待答事项;另外两个测试模块和文档变更均包含在完整评审范围。现有通知只显示最新终态摘要和总数,不宣称展示全部 scope 历史。
对主干的风险 / Risks
未发现当前 head 的阻断性代码问题。最强反例仍是原位替换,而不是 helper 中另造一份结果:真实 add_goal_todo/complete_goal_todo/list_goal_todos 先写 A reject、B reject,再 A cancel,经 exact source、native planner、原出站工作器和 synthetic provider 核验。File、SQLite 当前发送/核验均为 [1,1,1],generation [1,2,3];所有先前 delivered receipt 的文件哈希均未改变。旧 head 的 [1,1,0] / [1,2,2] 仍留作历史失败。
| 本地验证 | 结果 |
|---|---|
| 完整 native owner/Goal attention | PASS,29 项 |
| 完整 Python owner attention | PASS,42 项 |
| 原独立真实 completion/worker 反例 | PASS,File、SQLite 两项;输入和断言未改 |
| 不可变 base/current ordinary、disabled planning | PASS,200 组输出逐字相同 |
| typecheck、Ruff、diff-selected premerge | PASS,6 direct +11 selected checks |
上述全套还覆盖新 Goal/新任务、部分来源不可读、撤回、重启、pending/unconfirmed、binding/grant 变更、正文过大和关闭时原四个 delivery slots;没有把 delivered receipt 保留误当成新结果已送达。旧 subject/receipt 和普通输入有实际消费者,保留兼容合理;新增 identities 是 canonical state 的派生投影,不应让用户再手工维护一份状态。
语义与 CI 对齐
这是既有通知投影的词汇扩展,不是新的 actor lifecycle 或执行权限模型。终态文本提供信息;source、grant 和 effect checks 仍是强制边界。读取过时或不可用来源不会清空工作,也不因一次安全等待而新造监控/工作。未读取、轮询或等待远端 CI;以上是源码快照的本地结果,严格 change-quality 与 merge-readiness 仍分别检查。
我的整体评价 / Verdict
APPROVE,准确 head c031e44a1aa52ab28be221871c3503bc372d0b66;原 P2 已由两种真实后端上的相同反例证据关闭,完整当前范围未发现新的 blocker。long_horizon 和 user_experience 的有界判断均为 improved:同一待办后续变化现在能继续形成一次通知,用户无需增加设置、准备或确认轮次。这里没有声称持续净收益、实际模型采用或父级 Goal 已完成。
保持现有 TypeScript 决策 owner、Python 来源/投递 IO 与原出站工作器,完整身份作为派生数据进入摘要即可;不需要新开关、监控或并行生命周期。交付当前终态通知及中断/重放/缺失源边界;不验收真实账号、客户端视觉或安装后采用。复用既有 receipt/native owner 和 bounded helper 的成本与问题相称,不要求整套 TypeScript 重写或另设兼容框架。旧约束/未知和历史红色结果不被新的测试数覆盖。
视觉检查不适用于页面布局:该 diff 没有渲染组件变化;我检查了新纯文本及实际 synthetic worker/receipt 结果,真实 Lark 客户端完整会话、真实账户和安装后采用仍未验证。批准不等于已经合并或授权合并。
English verdict: APPROVE at c031e44a1aa52ab28be221871c3503bc372d0b66. The original File/SQLite in-place cancellation regression now passes; 29 native tests, 42 Python tests, 200 ordinary/off comparisons and local premerge pass. Live-account/installed adoption and merge gates remain separate.
Goal And Delivered Outcome
loopx-project/loopx:main. No issue closure claimed.Author Declaration
Implemented against
origin/main.loopx/extensions/lark/owner_attention.pytests/test_lark_owner_attention.pyloopx/control_plane/presentation/owner_attention.tstests/control_plane_ts/owner_attention.test.ts;tests/test_lark_owner_attention.pyloopx/control_plane/presentation/owner_attention.ts;loopx/extensions/lark/owner_attention.pytests/control_plane_ts/owner_attention.test.ts;tests/test_lark_owner_attention.pyloopx/control_plane/presentation/owner_attention.tstests/control_plane_ts/owner_attention.test.ts;tests/test_lark_owner_attention.pyloopx/control_plane/presentation/owner_attention.tstests/control_plane_ts/owner_attention.test.ts;tests/test_lark_owner_attention.pytests/control_plane_ts/goal_attention.test.tstests/control_plane_ts/goal_attention.test.ts(regression coverage; production projection unchanged)docs/product/periodic-reports.mdScope And Continuation
Validation
unitpassedtests/control_plane_ts/owner_attention.test.ts+tests/control_plane_ts/goal_attention.test.ts: 29 passed, 0 failed (terminal subject, latest-outcome selection, count-driven new generation, in-place scope replacement advancing the generation while identical identities stay stable, supersede on resolution, delivered receipts unchanged, oversize body marked failed).integrationpassedtests/test_lark_owner_attention.py: 42 passed through the existing outbound worker with native File authority fixtures.regression_paritypassedstaticpassedloopx canary premerge --from-git-diff --git-diff-base origin/main(standard tier): diff hygiene, RFC bilingual check, py_compile, maintainability ratchet, semantic vocabulary drift and risk-profile smokes passed; merge gate passed.decision_scopeare notified by design (fail-safe), documented indocs/product/periodic-reports.md.See validation disclosure guidance.
Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Shared-authority RFC fixture impact
Boundary Checklist
none.Signed-off-bytrailer (git commit -s).