Skip to content

fix(goal): surface declared vision fallback gaps in frontier projection - #3979

Merged
huangruiteng merged 7 commits into
loopx-project:mainfrom
now-ing:fix/iss-3916-vision-fallback-gap
Sep 7, 2026
Merged

huangruiteng merged 7 commits into
loopx-project:mainfrom
now-ing:fix/iss-3916-vision-fallback-gap

Conversation

@now-ing

@now-ing now-ing commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

A fallback direction declared in the goal vision used to disappear silently once the primary path blocked: the blocked-successor wait state clears acceptance gaps, leaving the fallback text with no selectable successor and no structured channel to surface it. This adds an advisory read-path projection check: when the primary lane is blocked and none of the three dispositions holds (a runnable Todo referenced via todo_delta, a bounded create-reopen successor, or an explicit terminal no-follow-up disposition via the closed-state family / path_delta.outcome=stop), exactly one vision_fallback_unresolved gap is projected into the new independent fallback_gaps field — which the wait state never clears and which does not feed the acceptance-gap replan flow.

Advisory only, per the issue's suggested starting point ("one projection warning plus a focused fixture"): no obligation wiring, no new schema, no new state machine.

Issue Or Task

Closes #3916

Validation

  • python3 -m pytest tests/control_plane/test_goal_frontier_fallback_disposition.py — 6 passed, one per acceptance check:
    • blocked primary + declared runnable fallback → fallback is selectable end-to-end (build_quota_should_run returns decision=="run" with selected_todo==<fallback id>)
    • declared fallback with no runnable/terminal disposition → acceptance_gaps==[] under the wait state while fallback_gaps carries exactly one gap (the issue's core bug scenario)
    • valid terminal disposition (state=no-followup / path_delta.outcome=stop, parameterized) closes the gap, and a second projection does not regenerate it
  • goal_frontier / vision / replan suites → 160 passed; all goal_frontier|build_quota_should_run dependent tests → 876 passed (1 pre-existing environmental failure unchanged on the untouched baseline)
  • test_m6_quality_gates.py → 3 passed; test_cli_output_budget.py → 21 passed; test_cli_output_differential.py → 33 passed (fallback_gaps does not enter the CLI rendering surface)
  • ruff check clean on all three changed files; red/green probes: disabling gap generation, the terminal check, or the runnable-resolution each turns the matching acceptance fixture red

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Refactoring
  • CI / build improvements

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • CLI (loopx/ command surface)
  • Dashboard (apps/presentation/dashboard)
  • Extensions (Lark, DingTalk, ...)
  • Docs / examples
  • Other:

Technical Direction

  • Core control-plane hardening
  • Target base branch: main
  • Direction tracker or promotion unit: —

Boundary Checklist

A fallback direction declared in the goal vision used to disappear
silently once the primary path blocked: the blocked-successor wait
state clears acceptance gaps, so the fallback text stayed in the
vision while the scheduler had no selectable successor for it.

Add an advisory read-path check that keeps each declared fallback
accounted for at projection time. When the primary lane is blocked and
none of the three dispositions holds (a runnable open Todo referenced
via todo_delta, a bounded create/reopen successor declaration, or an
explicit terminal no-follow-up disposition via a closed-family state
or path_delta.outcome=stop), exactly one vision_fallback_unresolved
gap is projected into the independent fallback_gaps field, which the
wait state never clears. The field is advisory only: it does not enter
the acceptance-gap replan stream and does not render into CLI output
surfaces.

Focused fixtures cover the three acceptance checks from the issue:
blocked primary plus a runnable fallback stays selectable, an
unresolved declared fallback projects exactly one actionable gap, and
a valid terminal disposition closes the gap without regenerating it.

Signed-off-by: now-ing <now-ing@users.noreply.github.com>
Drop the schema_version field from the vision fallback gap: the sibling
acceptance-gap records in the same projection carry only their typed
kind, and the repo's versioned schemas all live under the loopx_ prefix,
so a standalone goal_fallback_gap_v0 constant deviated twice for no
consumer.

Signed-off-by: now-ing <now-ing@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

动机

结论:REQUEST_CHANGES,精确 head 022263c218189c2ef5140b6d56bfd9ba68c001b9。两个 P2 正确性问题阻断了本 PR 的核心验收,并非可选风格建议:

  1. [P2] 英文单词出现不等于声明了 fallback。 fallback_disposition.py:44-54 只在自然语言里匹配 \bfallback\b。生产 projection fixture 中,“No fallback is authorized; wait for the primary prerequisite.” 被投影成需要补建/保留 fallback 的 gap;“主路径阻塞时,执行已声明的备用方案。”却完全没有 gap。前者与声明相反,后者继续静默丢失真实方向。最小修复是从明确的结构化声明/现有 path-disposition 关联读取意图;不要用扩充中英文关键词或否定词列表来补第二套 prose 分类器。需加入无 fallback、否定/引用文本、多语言声明的反例。
  2. [P2] 其他 Agent 的主路径 Todo 被当成当前 fallback 已解决。 fallback_disposition.py:91-108,175-178 汇总 backlog 等槽位,只有 open/advancement 检查,没有当前 Agent 可选性,也没有声明方向到具体 Todo 的关联。复现:当前 Agent 的主 successor 仍 deferred,另一个 Agent 持有 primary prerequisite;vision todo_delta 同时 retain 这两个主路径 Todo,根本没有 fallback Todo,结果 fallback_gaps 消失。最小修复应复用权威的 Agent-scoped eligible/selectable frontier,并确认 Todo 与这个 fallback 的对应关系;不能把任意 todo_delta ID 和任意 open backlog 的交集当作完成证据。

Issue #3916 要求声明的 fallback 在主路径阻塞后仍有可执行后继或有证据的终止处置,允许从一个警告开始。这个目标有价值:文字里的备用方向不应消失在 blocked-successor wait 中。但当前实现既不能可靠识别“声明”,也不能证明“这个 Agent 有对应工作”,所以不能以新增字段本身作为 issue 已解决的证据。

改动思路

should_run_prepare.py 调用 build_goal_frontier_projection_context_from_status,读取最新 agent vision 和 Todo summaries。已有 wait state 会清空普通 acceptance_gaps;PR 在这个步骤旁调用新 helper,再将最多一个 gap 通过两个 builder 参数传入最终 goal_frontier_projection.fallback_gaps。这是实际 quota 生产路径,不是仅供测试的模块。

正向路径:英文声明、主路径等待、没有匹配 Todo/终止处置 → 生成一个 JSON advisory gap,普通 acceptance_gaps 仍空、replan_required 仍 false。已有真正可选且被引用的 fallback Todo 时,quota 的原有选择规则选中它。负向路径:英文否定句也被当作声明;或另一个 Agent 的主路径 Todo 命中宽泛集合使 gap 消失。新字段不接入义务和重排,因此不会直接启动执行,但会误导读取它的操作者/Agent。

具体改动

三个文件共 +455/-0,无机械搬移:新生产模块 199 行,既有 frontier 模块增加 21 行接线,235 行 pytest fixture。没有 UI、renderer、CLI 文案或 schema 文件改动。

关键代码讲解

  • fallback_disposition.py:44 _declares_fallback_direction 检查 vision_patch 的 acceptance/vision 文本及顶层 vision_summary。这里是新增领域判断的事实来源,目前是 regex,不是 typed declaration,见第 1 项。
  • fallback_disposition.py:91 _summary_runnable_open_todo_ids 从五类 summary 槽提取 ID;_item_is_runnable_open_advancement 重判 status/task_class。它的“runnable”弱于调度器的可选性,且缺少 agent_id 输入;原 quota fixture 的跨 Agent 情形揭示了差异。
  • fallback_disposition.py:140 declared_fallback_gap_from_agent_vision 先排除 closed-family/stop,再检查声明与 blocked 主路径,随后通过 Todo ID 交集或 create/reopen 文本 delta 消除 gap。最后生成固定 reason_code/recommended_action,最多三个 unresolved IDs;generated_at 也做了长度限制。终止状态复用现有 helper 是正向的;todo_delta actions 却复制了 sibling 常量,应收敛到同一个 owner。
  • goal_frontier/__init__.py:1475 的 context builder 接入 helper;build_goal_frontier_projection 只添加 fallback_gaps,不改变 acceptance/replan 分支。这证明“advisory”描述与当前机器执行行为一致,但不证明 gap 的内容正确。
  • test_goal_frontier_fallback_disposition.py 的六项测试覆盖英文 happy path、无英文关键字、closed/stop、create delta 和已有可选 fallback;未覆盖否定文本、多语言或其他 Agent 的主路径引用。创建 delta 无实际 Todo 的探索探针另有普通 acceptance/replan gap,因此本次没有将它报成“完全静默”的独立 finding。

对主干的风险

本地原新增测试 6 passed,frontier/replan/blocked-successor/vision succession 合计 117 passed;三个独立语义探针 3 failed(否定句、多语言、跨 Agent 主路径误作 fallback)。Ruff 和 diff hygiene 通过,远端旧精确 head 的 CI 执行检查成功。未运行全仓测试或模型调用;本次使用合成 quota fixture,通过实际 production projection builder 复现,没有修改活动目标或注册表。

无 opt-in/default-off 声明,此 advisory 会在正常 projection 路径默认计算;未新增 actor 权限,generic 文案无业务领域限定,不制造 user gate,也未把强制义务伪装成 guidance。主要风险是输入语义和所有权被弱化,不是权限升级。新增 schema 形状虽为可选字段,仍是输出合同变化,PR body 已披露;现有 Markdown renderer 和 Workspace 没有消费 fallback_gaps,所以本 PR 本身没有可展示的前端改进,不应以它关闭“用户看到了警告”的验收。

我的整体评价

警告式切入比新建 fallback 工作流引擎合理,原 blocked wait 和 terminal helper 的复用也应保留。但“声明”与“可执行关联”这两个核心事实不应靠 regex 和第二套宽泛 Todo 分类来猜。按原 issue 重新评估,新增 199 行领域逻辑只有在这些语义可验证时才值得维护。相关简化建议是复用结构化 path/linkage 与权威 selectable frontier,并收拢重复 action 常量;不要为了修关键词识别继续扩张词表。

当前 hold,修复上述正确性缺口后再按新 head 复审。没有改代码或合并此 PR。就发布排序而言,这是后端 JSON 诊断补充,优先级低于已经验证的个人工作区交互与可靠性修复。

English verdict: REQUEST_CHANGES at 022263c218189c2ef5140b6d56bfd9ba68c001b9. P2 blockers: prose keyword matching invents a fallback for an explicit negation and misses a Chinese declaration; an unrelated other-agent primary Todo can suppress the missing-fallback warning. 117 existing focused tests pass, while three independent production-projection probes fail; Ruff and diff checks pass. Reuse structured declaration/linkage and agent-scoped selectable frontier rather than extending keyword heuristics. Advisory JSON only; no Workspace UI improvement or merge claimed.

The prose fallback-word matcher invented a gap for explicit negations
(No fallback is authorized) and missed non-English declarations. A
declaration now requires a structured todo_delta linkage entry
(activate/resume/retain) outside the blocked-successor wait scope;
prose text alone never projects a gap.

Completion evidence now reuses the authoritative agent-scoped
selectable advancement frontier (slot order and item predicates of
todo_advancement_frontier_counts), so a peer-claimed primary
prerequisite retained in todo_delta no longer suppresses the
missing-fallback warning, and linked non-advancement work does not
count as resolution.

Converge the todo_delta action constants and the entry parser into
fallback_disposition as the single owner, re-exported by the package
root, removing the duplicated sibling constant sets.

Signed-off-by: now-ing <now-ing@users.noreply.github.com>
@now-ing

now-ing commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the precise probes — both P2s are fixed at d7985ff3 (3 files, +274/-125), and your three reproduction scenarios are now pinned as tests.

P2-1 (word occurrence ≠ declaration). The \bfallback\b matcher is gone, with no keyword/negation list added. A declaration now comes only from the structured path-disposition linkage that already exists: todo_delta entries with activate/resume/retain actions, excluding the blocked successors the wait state itself is waiting on (they are the wait, not a fallback direction). Prose never declares — your negation fixture ("No fallback is authorized...") projects no gap, and the Chinese declaration conservatively projects no gap either, since the vision contract currently has no structured declaration field (path_delta.outcome has no fallback value; vision_patch is all prose). That deprecation is deliberate per your guidance: until a TS-side structured field lands, the warning is only answerable to structured declarations, rather than a second prose classifier.

P2-2 (other-agent primary Todo as resolution). Completion evidence is no longer an arbitrary todo_delta × open-backlog intersection. It now requires the declared linkage id to sit on the authoritative agent-scoped selectable advancement frontier — a new id-level mirror of todo_advancement_frontier_counts (same slot order, same item predicates: actionable-open + advancement + claim ownership + agent exclusion), with a consistency test against the authoritative counts. Your cross-agent probe now keeps the gap and reports the peer-held prerequisite as the unresolved declared id; a linked monitor Todo no longer counts either.

Nit. The duplicated action constants and the two divergent action:todo_id parsers converged into fallback_disposition as the single owner (re-exported at the package root).

Verification: 13 tests in the focused suite (including all three probes), 235 across the frontier/replan/blocked-successor/vision domain, 261 with consumers and m6 gates, 14 architecture, 43 on the TS pi_goal_loop_runtime path; m6 headroom is 196 lines on goal_frontier/__init__.py. One behavior change to flag: positive English prose declarations no longer project a gap either — only structured todo_delta declarations do, which keeps all three issue acceptance checks passing in their structured form.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

动机

结论:REQUEST_CHANGES,精确 head d7985ff38735363531ecb51ea640706731570a6a。本轮修复方向有两点明显进步:删掉了 prose 关键词分类器,否定句不再误报;同时补上了 Agent claim / task class / exclusion 过滤。13 个 focused tests、Ruff 和远端 11/11 checks 都通过。但核心 fallback 关联仍没有被结构化表达,且我新增的生产 projection 探针证明一个不相关的 create 可以让真正缺失的 fallback 再次静默消失,因此当前仍不能关闭 #3916。

改动思路

当前实现把 todo_delta 的 activate/resume/retain 解释为“声明 fallback”,把任意 create/reopen 解释为“fallback 已有 bounded successor”,再把这些 ID 与一个 Agent-scoped advancement ID 集合求交。这个模型比自然语言正则稳定,但 todo_delta 本身只是通用 Todo 变更日志,没有 fallback 类型、方向 ID 或 declaration→successor 关系;它无法回答“哪个 Todo 对应哪个 fallback”。

具体改动与阻断项

  1. [P2] 不相关的 create/reopen 会全局关闭 fallback gap。 fallback_disposition.py:203-205 只要看到任意 create/reopen action 就直接返回 None,完全没有比较 successor ID 与已声明 fallback linkage。独立复现:blocked primary;retain:todo_declared_fallback;没有该 Todo;再加入 create:todo_unrelated_maintenance。当前 projection 得到 fallback_gaps=null、acceptance_gap_count=0,只剩 blocked-successor wait state——正是 #3916 要防止的静默消失。最小修复必须把 create/reopen 与具体 fallback 声明做 typed 关联,并加入“unrelated create 不得消除 gap”的负向 fixture。
  2. [P2] 通用 Todo linkage 仍被当作 fallback declaration。 新测试 test_other_agent_primary_todo_does_not_resolve_the_gap 的注释明确写着“there is no fallback Todo at all”,却要求 peer-held primary prerequisite 生成 vision_fallback_unresolved。也就是说普通 primary-path retain 被重新命名成了 fallback 声明,产生语义假阳性;另一方面,真正仅存在于 vision prose 的声明现在全部被忽略。请增加一个明确的结构化 fallback declaration/relationship(可在 agent vision 或既有 task-graph/linkage contract 上做最小 typed 扩展),再据此逐个验证 runnable/create/terminal disposition;不要继续从通用 todo_delta 猜关系。
  3. [P2] “复用 authoritative frontier”目前仍是复制算法。 agent_scoped_selectable_advancement_todo_ids 重新实现了 todo_advancement_frontier_counts 的 slot precedence 和 predicates;测试只证明当前单个 fixture 的计数一致,不能防止未来一个 owner 修改 slot/predicate 后另一份漂移。建议让 projection 的一个共享 helper 返回 authoritative eligible items/IDs,再由它派生 counts 与 fallback resolution,消除第二事实源。

对主干的风险

这条路径默认在每次 goal frontier projection 中执行,虽是 advisory JSON、没有直接接入 obligation/replan,但消费者会把 fallback_gaps 当作结构化事实。当前会同时出现两类错误:普通 Todo 被标成 fallback;真正缺失的 fallback 被一个无关 create/reopen 掩盖。后一类已通过 production builder 复现为 acceptance gaps 也被 wait state 清空,所以不是仅展示层的小偏差。改动共 3 文件 +616/-12,新增 239 行领域判断;在关系未 typed 之前维护成本和误导风险偏高。

我的整体评价

方向仍然正向:独立 advisory field、复用 terminal disposition、避免制造 user gate、保持 fallback gap 不进入普通 replan stream,这些都值得保留。当前 blocker 很集中,不需要扩成 fallback workflow engine:补一个最小 typed declaration→successor 关系,按关系验证 disposition,并把 selectable frontier IDs 收回同一个 owner。修复后我会按新 exact head 复审。

English verdict: REQUEST_CHANGES at d7985ff38735363531ecb51ea640706731570a6a. The prose heuristic is gone and agent-scoped filtering improved, but the fallback relationship is still not represented. An unrelated create:todo_unrelated_maintenance suppresses the real missing-fallback gap entirely under the blocked-successor wait state, while a generic retained primary prerequisite is treated as a fallback declaration even when the test says no fallback exists. Add a typed declaration-to-successor relation and derive selectable IDs/counts from one authoritative helper. 13 focused tests, Ruff, and all 11 remote checks pass; the independent negative production-projection probe fails semantically.

@now-ing

now-ing commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

已按最新 review 意见修复,更新至 head 496166cf5ad5220ec7a96378a72621d6d22265ec,逐项说明如下:

  1. [P2] 不相关的 create/reopen 会全局关闭 fallback gap:已修复

    • 移除了只要出现任意 create/reopen 就全局返回 None 的逻辑,改为针对每个已声明的 fallback 做 typed 关联校验:仅当 create/reopen 的 Todo ID 属于该 fallback 声明的候选集(或明确关联的 successor)时才视为已解决。
    • 新增负向回归测试 test_unrelated_create_does_not_resolve_fallback_gap,验证在 primary 阻塞且声明了 fallback 时,无关的 create:todo_unrelated_maintenance 不会消除缺失 fallback 的 advisory gap。
  2. [P2] 通用 Todo linkage 仍被当作 fallback declaration:已修复

    • 不再从通用的 todo_delta(activate/resume/retain)去猜测 fallback 声明。在 agent_vision 中增加结构化的 fallback_declarations / fallback_relationships 字段(以及 blocked item 的 linkage contract 扩展),据此结构化提取 declaration 与 successor/target 关系。
    • 修正了原有测试预期并拆分:
      • test_other_agent_primary_todo_without_fallback_declaration_generates_no_gap:验证普通 primary 路径的 prerequisite retain 不会被误判为 fallback 声明,消除假阳性。
      • test_other_agent_primary_todo_does_not_resolve_the_gap:验证在声明了 fallback 时,由 peer 占有的 primary prerequisite 不会错误消除该 fallback 的 gap。
    • 新增 test_typed_declaration_successor_relation_resolves_gap 与 test_fallback_declared_via_todo_linkage_contract_projects_gap。
  3. [P2] “复用 authoritative frontier”目前仍是复制算法:已修复

    • 在 loopx.control_plane.todos.projection 中抽取统一的共享 helper todo_advancement_frontier_items,负责 slot precedence(executable backlog 优先,缺省时回退 unclaimed priority + claimed advancement)与 claim 归属过滤。
    • todo_advancement_frontier_counts 与 agent_scoped_selectable_advancement_todo_ids 均由该 helper 直接派生,彻底消除第二事实源与潜在漂移。

本地验证:

  • 17 个 fallback disposition focused tests 全数通过;
  • tests/control_plane 全套 2487 个测试全部通过;
  • ruff check 对修改文件完全 clean。

… frontier items

Add a structured fallback declaration-to-successor contract to resolve
fallback directions without guessing from generic todo_delta actions.
An unrelated create/reopen action no longer suppresses declared fallback
gaps, and generic primary-path retain actions no longer trigger false
positive fallback warnings.

Derive both agent_scoped_selectable_advancement_todo_ids and
todo_advancement_frontier_counts from a single authoritative helper
todo_advancement_frontier_items in projection.py, eliminating duplicate
precedence and predicate logic.

Signed-off-by: now-ing <now-ing@users.noreply.github.com>
@now-ing
now-ing force-pushed the fix/iss-3916-vision-fallback-gap branch from 496166c to 1e0e20d Compare September 6, 2026 15:49
@now-ing

now-ing commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the round-3 probes — all three findings are addressed at 1e0e20da, each pinned by your counterexamples.

Typed declaration→successor relation. A fallback direction is now a typed relation instead of an interpretation of generic todo_delta actions: a fallback_declarations entry on the structured vision linkage names its successor todo id explicitly, and disposition is evaluated per declared relation (runnable reference / bounded create-reopen successor / terminal disposition). Tests pin the three shapes you required: test_typed_declaration_successor_relation_resolves_gap, test_fallback_declared_via_todo_linkage_contract_projects_gap, and test_declared_bounded_successor_delta_resolves_the_gap.

Unrelated create/reopen no longer suppresses the gap. Resolution now requires the create/reopen action to reference the declared successor, not merely to exist. Your probe — blocked primary, a declared fallback that is missing, plus create:todo_unrelated_maintenance — still projects the gap (test_unrelated_create_does_not_resolve_fallback_gap), and a peer-held linked monitor Todo keeps it too.

No more false positives from generic retains. A retained primary prerequisite with no fallback declaration projects nothing (test_other_agent_primary_todo_without_fallback_declaration_generates_no_gap) — the old test that demanded a gap while its own comment said no fallback exists is inverted to match the typed model.

One frontier owner. The selectable-advancement ids are no longer a re-implementation: todos/projection.py now exposes the authoritative eligible-item selection, both the frontier counts and the fallback resolution derive from it, and test_selectable_frontier_ids_mirror_the_authoritative_counts plus test_vision_todo_delta_actions_contract_stays_the_shared_owner pin the single-source contract (the shared action constants also live in one owner now).

Verification: 17 tests in the focused suite (13 prior plus the four new probes/inversions), 30 across the maintainability ratchet and CLI output budget gates, Ruff check and format clean on all changed files. CI is running on the exact head; the round-2 red/green probes still pass with the new relation model in place.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

动机

REQUEST_CHANGES,精确 head 1e0e20da0684fef09518ba842eb4e385dcb1176e。旧的 prose 猜测与 unrelated-create 消除 gap 已改善,但新 declaration 仍没有接通实际写入/读回路径。

[P1] 真实 Vision 入口丢弃新 declaration,测试绕过它后才出现 gap。 fallback_disposition.py:116-123 消费 fallback_declarations 等新字段,semantic_history.py:170-173 只从收到的历史对象转发两个字段;未改动的 normalize_goal_vision_packet() 经真实 TypeScript goal.vision_checkpoint.evaluate prepare 后将 declaration 丢弃,compact_goal_vision_packet() 也会丢弃它。用相同合成 Vision 经 production preflight 后再调用 production frontier builder,结果 acceptance_gaps=[] 且没有 fallback_gaps;直接使用测试手填的 raw history 则有 gap。实际 caller 仍会遇到本 issue 要解决的静默消失。最小修复是让一个有界、唯一的 typed declaration 通过既有 TS Vision 写入验证、持久化、status/shared-runtime compaction 和历史读回;补真实 write→独立 readback→quota/frontier 的 fixture,不要仅继续给 reader 加别名。Todo fallback linkage 的新字段同样需要证明受支持的 authoring/metadata 路径;当前新增测试只是直接向 summary dict 塞字段。

改动思路

Issue #3916 的目标有价值:主路径阻塞后,明确声明的 fallback 不能消失。PR 保留独立 advisory fallback_gaps,不制造 user gate,也不把 gap 强行送入 acceptance replan stream。最新 head 把“声明”从 generic todo_delta 分离,并让 selectable IDs 与 counts 复用 items owner。这两点优于上轮实现。

正向路径在直接构造的已含 declaration 历史中成立:primary 等待、fallback 缺失 → 单个 bounded gap;存在本 Agent 可选 successor → 无 gap;无关 create 不再清除 gap。负向的真实路径却是:caller 输入 declaration → TS preflight/compact read model 丢字段 → semantic_history 没有可转发的数据 → parser 得不到声明 → wait state 清空普通 gaps 后无任何 fallback 提示。

具体改动

五文件 +1014/-69:新增 375 行生产 parser/projection、470 行测试,frontier 接线、history 字段转发与 Todo projection 提取;不是纯展示 warning 的几行变更。

  • FallbackDeclaration 记录 declaration、target、successor,candidate_todo_ids 联合三者。关系比 prose 明确,但支持三个顶层别名、patch 别名、多个字段别名及箭头/冒号字符串没有已验证的生产 caller,建议收敛为一个受写入契约承认的形状。
  • declared_fallback_gap_from_agent_vision() 复用 closed-state 与 blocked-successor helper,再按 selectable IDs/create/reopen 判断 disposition。仍需审查其减去 waiting IDs 的语义:显式声明本身指向等待中的 Todo 时,probe 得到无 gap;“不可执行”不应自动等同“已解决”。目前先由生产 declaration 接线问题阻断。
  • todo_advancement_frontier_items() 成为 items/IDs/counts 的共同来源,删除上轮第二套 predicate 的方向正确;原数字摘要 lower bound 仍在 counts 保留。不要把有计数就等价于可恢复所有具体 ID。
  • semantic_history.latest_agent_vision_from_runs() 和三个 frontier builder 只负责读回/转发,不能替代前置的 typed write owner。
  • 新测试涵盖 peer-owned/monitor/unrelated-create/terminal 等边界,但 raw fixture 注入跳过了真实 Vision prepare 和 status compaction,解释了为何 17 项绿灯仍漏掉核心路径。

对主干的风险

独立验证:17 focused tests +128 adjacent frontier/Vision/checkpoint tests 全部通过,Ruff 通过;独立 probe 调用真实 TS preflight,并分别对 raw、prepared、compact Vision 运行 production frontier builder,只有 raw fixture 能保留 declaration/gap。未修改活跃 Goal,也未把此局部证明当成完整 CLI 持久化验收。完整写入/readback、全部计数提取的 baseline parity 仍未验证,不能据此批准。

这是默认 read-path 行为,没有 opt-in 声称,也没有新 peer authority。Advisory 与 must-attempt obligation 的分离保持正确;但新领域关系必须由已有 typed TS 边界接受,不能在 Python reader 中自行假定一套持久协议。原 issue 要一个可用 warning,目前代码量增长而可用入口未成立,value/maintenance 比例尚未证明。

我的整体评价

保留独立 advisory field 和共享 frontier-items 提取;删除无真实 caller 的兼容别名,先完成最小声明形状的 production write/readback 闭环。Future-facing pass 应落在既有 Vision owner,而非继续扩展 reader parser。这次不是要求把 warning 扩成 fallback workflow engine,而是要求新增的关系能被产品真实保存和读取。当前不能关闭 #3916。

English verdict: REQUEST_CHANGES at 1e0e20da0684fef09518ba842eb4e385dcb1176e. The real TS Vision preparation and status compaction drop the new fallback declaration fields; only raw test-history injection reaches the new warning. Independently reproduced the missing gap after production preparation, with 145 focused/adjacent tests and Ruff passing. Wire one bounded typed declaration through the existing write/readback owner, and remove unsupported alias scaffolding before claiming the issue is solved.

…te contract

The real caller entry dropped the round-3 fallback_declarations: the
TS-owned goal.vision_checkpoint prepare whitelisted them away, and the
status/shared-runtime compact read model mirrored that loss, so only
tests that hand-filled raw run history could project a fallback gap.

- prepare now validates and carries one bounded shape: at most four
  entries, unique non-empty declaration_id, optional target_todo_id and
  successor_todo_id, each public-safe and 120-char bounded; over-wide,
  duplicated, unsafe, or id-less packets are rejected explicitly instead
  of silently losing the declared fallback direction
- compact_goal_vision_packet mirrors the same bounds so the persisted
  declaration survives status and shared-runtime read-model compaction
- the frontier parser now consumes only the contract-written shape; the
  fallback_relationships/fallbacks aliases, arrow/colon string forms,
  renamed-key fallbacks, and the un-authored todo-summary linkage fields
  are removed rather than kept as reader-only compatibility
- tests exercise the production chain end to end: caller packet through
  the real TS prepare and compact read model into run history, an
  independent semantic-history readback, and the quota/frontier
  projections; alias shapes stay non-declarative

Signed-off-by: now-ing <now-ing@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

动机

审查 head:54519bd0089adde8fdaa19940bcf6aeb11e46f37。结论:REQUEST_CHANGES。

[P2] 真实 refresh-state 的 run index 仍丢弃 fallback_declarations,后续状态读取看不到提醒。 新增 semantic_history.latest_agent_vision_from_runs 保留逻辑依赖索引存在该字段,但未修改的 loopx/state_refresh.py:_build_state_refresh_output_projections(533–543 行)仍按旧 allowlist 写 agent_vision,遗漏 fallback_declarations。新 compact helper 并没有接入这个实际 writer。

在隔离的合成 Goal 上运行真实 CLI refresh-state、TS prepare 和磁盘写入,命令 exit 0:响应和完整 run JSON 都有 declaration;runs/index.jsonl 和从索引恢复的 latest vision 都没有。将真实完整记录交给相同 blocked-summary 投影能得到 vision_fallback_unresolved,实际索引记录则没有该 gap。不是 parser 不接受,而是成功之后的持久化/独立读回断链。

原始 #3916 希望主路径等待时,显式声明的 fallback 不再静默悬空:关联可执行 Todo、形成有界后继,或明确终止。它要的是一个投影提醒,不是自动创建 Todo 或新的规划引擎。

改动思路

当前通过 typed fallback_declarations 表达意图,既有 Todo frontier 决定是否 runnable,独立 fallback gap 只做 advisory,不混入 acceptance、quota must-attempt 或用户 gate。方向合理。此前 TS prepare 丢字段的问题已修,但需要走完真实 writer → index → later consumer,而非只验证 normalize / compact 的组合。

最小修复应复用现有 compact serializer 或补齐实际 writer 的同一契约,避免维护另一套字段白名单。用真实 refresh CLI 写盘后,再从索引进行独立 status/quota 读取,证明等待场景出现提醒;不需要增加自动 mutation 权限。

具体改动

完整审查覆盖八个文件:frontier 入口、289 行 fallback_disposition helper、semantic_history、goal_vision、typed vision_checkpoint、Todo projection helper 抽取,以及 Python 和 TS 测试;整体约 +1249/-72。PR 描述原来的“无 TS / schema 改动”已不符合当前 head,应更新。

关键代码讲解

  1. normalizeFallbackDeclarations / vision checkpoint prepare:校验有界列表、唯一 declaration_id 和字段预算,写入 agentVision;真实 CLI 已证明本段现在可通过。
  2. compact_goal_vision_packet / latest_agent_vision_from_runs:前者新增字段保留,后者消费最新历史;问题是实际 run-index serializer 在两者之间没有传递字段。对应真实调用链不是测试手工组装的 run dict。
  3. fallback_disposition:typed declaration → blocked-primary 判断 → runnable successor 或 terminal disposition → 最多有限条 unresolved gap。不是用任意 prose 关键词判定,也没有代替 Todo owner 或直接创建工作。
  4. todo_advancement_frontier_items 和 frontier 入口:复用既有可执行筛选,排除 peer-owned、monitor、waiting 等项,并把 fallback gap 与其他诊断分开。该抽取同时服务计数与可选择 ID,必须维持既有筛选语义。

正向 helper 路径:显式 declaration 经 typed prepare / compact,被 blocked frontier 识别为未解决,或被已有 runnable / terminal 证据消解。真实负向路径:CLI 成功 → 完整记录正确 → 索引投影丢字段 → latest vision 无 declaration → 提醒消失。现有 _fallback_vision_run 测试手工拼装历史行,跳过的恰是出问题的 writer。

对主干的风险

113 项聚焦 Python 测试、16 项 TS 测试、Ruff 和 diff check 通过;GitHub 可见 checks 通过,发布任务按设计跳过。独立真实 CLI 在 exact base bf217e1e01bec79f357c9ecbd580cf2dfa73db8b 不保留声明,当前 head 只在响应/完整记录保留,索引仍不保留。正控制证明相同投影拿到字段后能输出 gap。

未跑全仓库,也未证明所有抽取后的 frontier 组合与基线逐项等价;不能把聚焦绿测当作全局兼容。该字段是显式输入,不授予自动执行权限;实际 Todo 创建并未由本 PR 实现,不能把 create/reopen 声明本身称为已完成创建。无新 PostgreSQL 存储实现。

我的整体评价

REQUEST_CHANGES。从“一条提醒”扩展到 typed schema、历史序列化、frontier 抽取后,应重新证明完整交付价值;当前 change proportionality 仍 not_yet_proven,因为真实后续消费者没有收到新字段,而不是单凭行数否定。前瞻性改进应收敛在已有 vision 序列化边界:减少重复字段知识,补真实持久化读回测试,保持提醒与强制义务分离。修复这个贯穿路径后再评估完整 head。

English verdict: REQUEST_CHANGES at 54519bd0089adde8fdaa19940bcf6aeb11e46f37. Real refresh-state accepts and returns fallback_declarations, but the production run-index writer drops them; later history-based consumers therefore lose the unresolved-fallback warning. 113 Python and 16 TS tests pass, as do visible CI checks, but an independent real CLI → disk → index readback reproduces the missing field. Reuse the actual serializer boundary and cover separate readback, not a hand-assembled history fixture.

@now-ing

now-ing commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the precise reproduction — it matched exactly. The real entry chain dropped the declaration at two whitelists: the TS-owned goal.vision_checkpoint prepare and the status/shared-runtime compact read model, so the reader-side forwarding never had data to forward.

54519bd0 wires one bounded typed declaration through the existing write owner end to end:

  • prepareVisionRefresh now validates and carries agent_vision.fallback_declarations: at most 4 entries, unique non-empty declaration_id, optional target_todo_id/successor_todo_id, each public-safe and 120-char bounded. Over-wide, duplicated, id-less, private-text, or over-long packets are rejected explicitly (EffectRuntimeRequestError) instead of silently losing the direction; the limits are also surfaced in vision_budget.field_limits/field_usage.
  • compact_goal_vision_packet mirrors the same bounds, so the declaration survives status and shared-runtime read-model compaction.
  • The frontier parser now consumes only the contract-written shape. The fallback_relationships/fallbacks aliases, arrow/colon string forms, renamed-key fallbacks, and the todo-summary linkage fields are removed — I verified the linkage fields had no production author (todos.py's fallback_todo_id is an unrelated positional fallback id), so they were reader-only scaffolding, per your note.
  • Tests now go through the production chain: the caller packet passes the real TS prepare and the compact read model into run history, with an independent semantic-history readback asserted field-by-field, plus the quota/frontier projections (fallback_gaps visible in build_quota_should_run). With the production diff stashed, those tests fail (7 failed) exactly as your probe described; restored, all 19 pass. TS validator tests cover the carry plus all six rejection classes.

Full run: 19 focused + 237 adjacent frontier/vision/checkpoint/quota tests, the maintainability ratchet, output budget/differential, 605 TS tests, typecheck, and Ruff all green; the only failures in the broad sweep pre-exist at the base head. goal_frontier/__init__.py is untouched this round. The one open review note you parked (waiting-id subtraction semantics when a declaration itself points at a waiting Todo) is intentionally left for a follow-up so this change stays minimal — happy to take it next if you want.

…list

The state-refresh run index still wrote agent_vision through the old
field allowlist, so a successfully prepared and persisted fallback
declaration disappeared from runs/index.jsonl and every later
semantic-history read missed the frontier reminder the declaration
exists to provide. The allowlist now carries fallback_declarations,
pinned by a builder-level index regression that feeds a real typed
declaration through and asserts it survives the index projection.

Signed-off-by: now-ing <now-ing@users.noreply.github.com>
The previous formatting pass expanded historical compact imports and
pushed the module past the maintainability ratchet line budget. Restore
the original layout and reapply only the fallback_declarations allowlist
entry, keeping the module at its previous 1479 lines.

Signed-off-by: now-ing <now-ing@users.noreply.github.com>
@now-ing

now-ing commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

Found and fixed at b76595807 — you were right that the write side was still lossy: the run index in state_refresh.py projected agent_vision through the old field allowlist, so a declaration that the TypeScript prepare accepted and the run record persisted cleanly still disappeared from runs/index.jsonl, and every semantic-history read after that missed it.

The allowlist now carries fallback_declarations. The regression goes through the real _build_state_refresh_output_projections builder with a typed declaration and asserts it survives the index projection field-by-field — the same gap your refresh-state CLI probe exposed (record and response carried the declaration, the index row did not). The module stays at its prior 1479 lines under the maintainability ratchet; an intermediate formatting pass that expanded historical compact imports and crossed the line budget was reverted in favor of a surgical one-line allowlist change.

Verification: state-refresh projection tests (including the new index regression) and the full fallback-disposition suite pass, maintainability ratchet green, CI 11/11 on the exact head.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

动机

APPROVE,exact head b765958076d5ed655261bab040696a762ff322b7,全量基线 bf217e1e01bec79f357c9ecbd580cf2dfa73db8b。此前 54519bd0089adde8fdaa19940bcf6aeb11e46f37 的 run-index 丢字段 blocker 已修,本轮独立的 CLI→磁盘→后续 quota 读回证明提醒确实可达。

#3916 要求主路径等待时,显式 fallback 不再静默悬空。目标是一条有界 advisory,不是自动创建 Todo、授予 peer 权限或增加强制执行任务。只有关键消费者真正收到声明,增加 typed input、serialization 和 frontier 的成本才有实际收益。

改动思路

Vision 中显式、限量的 fallback_declarations 由 TS prepare 验证;Python compact/history 保留;真实 refresh-state 的 index writer 本轮补上同一字段;后续 quota 从历史恢复 vision,再结合权威 Agent frontier 投影 unresolved gap。

比较 base/head 的 vision normalize/compact、run-index writer、semantic history、blocked-successor wait 和 Todo frontier。复用既有 runnable/ownership 判定,不另造任务状态源;fallback_gaps 不进入 acceptance-gap replan stream,因此可以在普通 acceptance gaps 因等待而清空时保留提醒,又不把提醒升级为 obligation。

具体改动

全量十文件 +1316/-75:frontier 入口与 289 行 helper、semantic history、Python vision compact、TS checkpoint、Todo projection 抽取、state_refresh,以及三份 Python/TS 测试。最新两文件 +67/-3,主要是 writer allowlist 与回归测试,核心机制没有再扩张。

  • normalizeFallbackDeclarations:列表最多四项、declaration_id 唯一、字段预算受控;prose 和普通 todo_delta 不会自动变成 fallback 声明。
  • compact_goal_vision_packet / _build_state_refresh_output_projections / latest_agent_vision_from_runs:串起 prepare、完整 run、索引、后续读回;本轮实际 writer 保留字段,补上上次只验证 helper 组合的缺口。
  • declared_fallback_gap_from_agent_vision:显式声明 + 主路径阻塞 + 没有 runnable/terminal/有界后继声明,产生独立 gap。peer-held 或 monitor 不能当作当前 Agent 可执行后继。
  • todo_advancement_frontier_items / counts / selectable IDs:共用 executable backlog 优先级和 claim/exclusion 规则,计数仍保留已有 summary/peer diagnostic floor;复用此边界比复制筛选器合理。
  • frontier 入口:最多一条 gap、有限 unresolved ID,不新增调度器或自动 mutation。create/reopen 在这里是有界后继声明的 disposition,并不证明 Todo 已实际创建,不应在产品描述中混称。

对主干的风险

真实反事实采用相同隔离 registry、文件 Todo、typed vision 输入:peer 持有 prerequisite,当前 Agent 的 primary Todo deferred,声明 fallback 尚无 runnable Todo。旧 54519bd 的 refresh 成功且响应保留声明,但 index=false,后续 quota 无 gap;当前 refresh/index 均保留,独立 quota CLI 输出 vision_fallback_unresolved,包含 exact fallback ID。两边普通 acceptance_gaps 都为空、replan_required 都是 false、primary 仍 waiting——没有借修复提醒改变执行义务。

直接 status 命令在该合成场景没有渲染此 frontier 字段;真正验证的最终消费者是随后独立的 quota CLI,不能把它写成所有 status 展示面都已验证。status/quota 的整体退出码 1 来自合成 Goal 的健康/等待状态,无 exception/error;refresh exit 0,目标字段正常读回。

本轮 21 focused Python +111 adjacent Python tests、16 TS tests、TypeScript typecheck、Ruff、diff check 通过。覆盖 runnable fallback、终止 disposition、无声明、无关 create、monitor/peer 以及 blocked-successor 分支;远端可见检查成功,发布任务有条件跳过。未跑全仓库或所有可能的旧缓存形状,不把这些结果等同于穷举证明。

无 opt-in/default-off 能力声明或新安装指令;这是既有 vision 的显式输入扩展。无声明不会生成 gap。错误与建议保持 Goal-neutral;typed declaration 不是 prose 关键词判定,advisory 与机器 obligation 清楚分离。

我的整体评价

原始需求到真实持久化消费者现在闭环,本轮不再维持“机制价值未证实”的阻塞。这个独立提醒的边界仍足够窄,并复用了已有状态所有者。

未来向整理建议收敛 vision compact 与 run-index 的字段白名单知识,避免下一个字段再次只修一个 serializer;但本次 surgical writer 修复已有真实读回证据,不必为了去重扩大重构。PR body 中“无 TS/schema 改动”等旧描述应更新。批准此 head,未合并。

English verdict: APPROVE at b765958076d5ed655261bab040696a762ff322b7. The production run-index writer now preserves fallback declarations. Paired real refresh/file/quota probes change the prior missing warning into the expected unresolved-fallback advisory while preserving waiting state and no replan obligation. 132 Python and 16 TS tests, typecheck, Ruff, diff checks and visible CI pass. Direct status presentation and all cache permutations are not claimed as fully qualified. No merge performed.

@huangruiteng
huangruiteng merged commit 12b270f into loopx-project:main Sep 7, 2026
11 checks passed
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.

Require declared vision fallbacks to resolve to runnable work or an explicit terminal disposition

2 participants