Conversation
A goal objective is user prose, but both state writers rendered it straight into the document body. An objective that collapsed into one fence line, opened a tilde fence, opened an HTML comment, or merely looked like a Todo row then hid or polluted the generated Todo sections below it: `parse_todo_source` reported no source sections and zero items, and a Todo-shaped objective example was adopted as real work. Wrap generated objective text in an isolated `loopx:objective-v0` region in `render_state_markdown` and in the project registry `_state_markdown`, and teach `visible_markdown_lines` to treat the enclosed lines as prose. Fences and comments outside the region still parse exactly as before, so fence handling is not weakened and Todo authority is unchanged. Legacy state documents without the region keep their current readback. Add `read_objective_text` so the dashboard goal context recovers the objective verbatim, including objectives that begin with an HTML comment, and keep the previous section scan as the fallback for unmarked state. Add tests/control_plane/test_objective_todo_visibility.py covering both writers for fenced, tilde-fenced, HTML-comment, and Todo-shaped objectives plus plain-text and closed-fence controls. Closes loopx-project#4401 Signed-off-by: Exusty <107248786+Exusty@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
动机
objective 是用户散文,但两个文档生成入口(bootstrap.render_state_markdown、projects/registry._state_markdown)都把它原样插进正文的 ## Objective 段。objective 里如果带 ``` / ~~~ 围栏、<!-- 注释开头,或只是"看起来像 Todo 行"的文本,读回时就会把下面生成的 Todo 段吞掉或污染(issue #4401)。本 head 的处理是:把 objective 包进一对标记注释形成的 region,并让 Todo 读取器跳过该 region。in-repo 读取路径因此被修好了,我确认这一点;但这次改动还带来了两个必须收口的问题。
改动思路
在 active_state_metadata.py 里新增 OBJECTIVE_REGION_BEGIN/END 两个标记、render_objective_block(把 objective 原样夹在两条注释之间)与 read_objective_text(按标记读回);两个生产者改用它,chat_server 读回时优先用 read_objective_text,legacy 文档回落到原来的 _active_state_section;todos/machine_region.visible_markdown_lines 增加 in_objective 分支,把 region 内的行整段跳过,于是 objective 的围栏与注释不再被当作文档结构,真实围栏仍按原规则解析。
具体改动
loopx/control_plane/goals/active_state_metadata.py(+42):新增 region 常量、render_objective_block、read_objective_text。loopx/control_plane/todos/machine_region.py(+19/-4):visible_markdown_lines增加 objective region 跳过逻辑。loopx/bootstrap.py(+6/-1)、loopx/control_plane/projects/registry.py(+6/-1):生产入口改用render_objective_block。loopx/chat_server.py(+6/-2):展示读回优先走read_objective_text,保留旧扫描器作为 legacy 回落。tests/control_plane/test_objective_todo_visibility.py(+131,新文件):覆盖 hostile objective(塌缩成单行围栏、tilde 围栏、未闭合注释、Todo 形态行)与对照 objective。
关键代码讲解
render_objective_block(active_state_metadata.py:40):输出<!-- loopx:objective-v0 begin -->\n{原文}\n<!-- loopx:objective-v0 end -->。注意原文没有任何引用或转义,隔离完全依赖读者认识这对标记。visible_markdown_lines(machine_region.py:57):进入 region 后整段continue,直到出现 END 标记;因此识别该 region 的读取器(LoopX 自己的 Todo 解析)确实不再被 objective 的围栏影响。read_objective_text:legacy 文档返回空串,调用方回落旧路径,展示面向后兼容。
对主干的风险
阻塞项 1(P1,既有注册被破坏):文档形态变了,但 registry.py:333 仍然是逐字节比较 if existing_state != matching_state:。我用真实 CLI 做了端到端反事实:先用 base 代码注册一个 project goal(状态文件为旧格式),base 再次注册成功(幂等);随后用 head 代码对同一个 registry / knowledge root 再注册 → {"ok": false, "changed": false, "error": "goal state file conflicts with registration: .../ACTIVE_GOAL_STATE.md"}。原因是 head 渲染出的文档(region 标记)与 base 渲染不同(我用两边 _state_markdown 直接比对确认不同),而 objective 是否为 hostile 无关——每一条 objective 都会加上标记行,所以升级后所有既有注册都会撞上这个冲突。最小修复:引入兼容比较(接受 legacy objective 段,仅对真正变化的正文报冲突),兄弟 head #4402 里的 _registration_state_matches 就是这个决策的现成 owner;同时补一条"从上一版渲染出的文档再次注册"的用例——本 head 的 93 个测试全绿,正因为它们都从空 knowledge root 开始,从不重注册旧文档。
阻塞项 2(P1,交付卫生):head 53cefbc046515be26d08ac83f1790a85670832e1 当前 mergeable=CONFLICTING、mergeStateStatus=DIRTY,并且远端没有任何 check run(check-runs total_count=0,commit status pending)。请 rebase 到当前 main 并重跑受影响 workflow 后再复审。
P2(同一处边界被两个节点重复交付):本 PR 与 #4402 改的是同两个生产者、同一条读取边界,却是两套文档约定(#4402 用逐行引用、本 PR 用 region 标记)。两个都合入仓库后,生成的文档要么被引用、要么被标记包裹,Todo 读取器必须同时理解两种。建议先收敛到一个实现:#4402 已经自带兼容比较与协议文档,收敛成本更低;若团队更偏爱 region 方案,也应把它并入那一条线并把另一条关掉。
P2(隔离只对认识该约定的读取器生效):objective 仍然以原始 Markdown夹在两条注释之间。我在 head 上渲染了 hostile objective 并做朴素围栏扫描:文档里仍然是那行 text Implement and validate the task. ,扫描结果是"该行打开了一个直到文件末尾都未闭合的围栏"——也就是说 GitHub / 编辑器预览 / 任何不认识 loopx:objective-v0 的消费者,仍然会把 objective 之后所有的生成段落渲染成代码块。机器解析路径修好了,但人读的那一面仍然是被吞掉的状态;要让文档本身中性,应当让 objective 不能打开任何结构(引用或围栏),这也正是 region 约定可以不必存在的前提。
其余非阻塞观察:region 是 fail-closed——文档丢掉 END 标记后,其后所有行对 Todo 解析都不可见;objective 中若出现一行恰好等于 END 标记会提前结束 region;loopx:objective-v0 这个新文档约定没有写进拥有该边界的协议文档 docs/reference/protocols/active-state-structured-projection-v0.md。
我的整体评价
REQUEST_CHANGES。机制本身是有效的:visible_markdown_lines 的 region 跳过确实让 hostile objective 不再吞掉生成的 Todo 段,head 的 93 个测试(含新文件)通过,chat 展示也保留了 legacy 回落,落点(goals 边界上下文 + todos 读取器)是对的。但这次改动把生成文档的形态变了,却漏掉了随之而来的兼容责任:真实 CLI 反事实显示,升级后对既有 goal 再注册会直接报 goal state file conflicts with registration,而这不是 hostile objective 才有——每条 objective 都会新增标记行;同时该 head 还处于 CONFLICTING 且远端零 check。再加上它与 #4402 是同一处边界的两套约定,而 region 方案对不认识该约定的 Markdown 读者仍然留下未闭合围栏的显示问题,因此我建议:先按 #4402 那条线收敛(或把 region 方案并入那条线并补齐兼容比较、协议文档与"文档本身中性"的保证),rebase 且让 workflow 在 head 上跑出绿色,再做一次 exact head 复审。
English verdict: REQUEST_CHANGES at 53cefbc. The region-based isolation does fix the in-repo reader (93 focused tests pass, and the hostile-objective cases no longer hide the generated Todo sections), but the head changes the generated document for every objective while registry.py:333 still compares documents byte-for-byte, so re-registering any pre-existing goal fails: an end-to-end CLI probe registers with the base code (second registration accepted) and then re-registers the same goal with the head code, which returns ok=false with "goal state file conflicts with registration". The head is also CONFLICTING/DIRTY against current main with zero check runs, and it duplicates the same boundary already being fixed by PR #4402 with a different document convention. Additionally the objective is still emitted as raw Markdown between two comment markers, so a standard Markdown reader still sees an unclosed fence swallowing the generated sections below it - the isolation only holds for readers that implement the new loopx:objective-v0 convention.
|
更早的 PR 4402 好像已经修了这个问题了 |
|
Closing as superseded by #4402. 按维护者此前指出的重复修复问题核对后,这个 PR 的问题定位和回归用例有价值,但当前没有需要独立合入的功能增量:
本次在上述 main 上运行 因此关闭这个重复实现,保留现有主干方案;不需要为相同修复再建后续任务。感谢提供复现及边界用例。 English disposition: Closed as superseded by merged #4402. Current main passes 104 focused tests and all 12 cross-writer checks derived from this PR's input classes. No independent functional gap was identified; the existing REQUEST_CHANGES on the unchanged PR head remains in effect. |
Summary
render_state_markdownand the project registry
_state_markdown) rendered it straight into thedocument body. An objective containing a Markdown code fence, tilde fence,
HTML comment, or text that merely looks like a Todo row then hid or polluted
the generated Todo sections below it.
parse_todo_sourcereturned{'user': None, 'agent': None}and0agent items. After the fix it returnsboth source sections and
1(the generated connection-validation Todo).loopx:objective-v0region inboth writers and teach
visible_markdown_linesto treat the enclosed lines asprose. Fences and comments outside the region parse exactly as before, so
fence handling is not weakened and Todo authority is unchanged; legacy state
documents without the region keep their current readback.
read_objective_text, used by the dashboard goal context, so theobjective is recovered verbatim — including objectives that begin with an HTML
comment, which the old section scan silently dropped. The previous section
scan stays as the fallback for unmarked state.
Issue Or Task
Validation
53cefbc04651finishedsyntheticunitpassedtests/control_plane/test_objective_todo_visibility.py(new): 39 passed. Covers both writers against fenced, tilde-fenced, HTML-comment, and Todo-shaped objectives plus plain-text and closed-fence controls; asserts Todo sections stay readable, the objective is preserved verbatim, direct readback recovers it, and a real fence below an isolated objective still hides what it encloses.unitpassedtests/control_plane, includingtest_todo_machine_region.py,test_todo_document_ownership.py,test_todo_next_action_settlement.py,test_goal_terminal_no_followup.py,test_start_goal_compact_projection.py): 344 passed, 4 failed.regression_paritypassed{'user': None, 'agent': None}/0; after the fix it prints both source sections /1.[sqlite]-arm tests intest_todo_projection_recovery.pyandtest_task_graph_topology.py. They failin this sandbox with
SQLite authority runtime is not qualified (SQLite 3.51.2 ...) ... Use the qualified Node 22.22.3 runtime, i.e. a runtime-qualificationgate in the local environment (Node 22.22.2 present, 22.22.3 required), not an
assertion about the changed code paths. I did not re-run them on a clean tree,
so treat them as environment-blocked rather than verified-unrelated. The full
tests/control_planesuite was not completed: it exceeds a usable runtime inthis environment. Untested paths: none identified beyond that.
Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
Core control-plane hardening
Long-horizon benchmark evidence
Operator surface and IM integration
Shared Goal Authority and cross-host coordination
Architecture and research incubator
Target base branch:
mainDirection tracker or promotion unit: N/A
Shared-authority RFC fixture impact
N/A
Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).none.Signed-off-bytrailer (git commit -s).