Skip to content

fix(bootstrap): isolate generated objective text from Todo parsing - #4435

Closed
Exusty wants to merge 1 commit into
loopx-project:mainfrom
Exusty:codex/4401-fenced-objective-todo-visibility
Closed

Exusty wants to merge 1 commit into
loopx-project:mainfrom
Exusty:codex/4401-fenced-objective-todo-visibility

Conversation

@Exusty

@Exusty Exusty commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • A goal objective is user prose, but both state writers (render_state_markdown
    and the project registry _state_markdown) rendered it straight into the
    document 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.
  • Reproduced from the issue: parse_todo_source returned
    {'user': None, 'agent': None} and 0 agent items. After the fix it returns
    both source sections and 1 (the generated connection-validation Todo).
  • Wrap generated objective text in an isolated loopx:objective-v0 region in
    both writers and teach visible_markdown_lines to treat the enclosed lines as
    prose. 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.
  • Add read_objective_text, used by the dashboard goal context, so the
    objective 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

Public-safe summaries only, including in HTML comments. Do not paste raw logs,
private data or prompts, private screenshots, credentials, internal URLs, connection
strings, or local paths.

  • Tested revision: 53cefbc04651
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
unit passed tests/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.
unit passed Related Todo/state regression set (18 files under tests/control_plane, including test_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_parity passed Before the fix the issue's own reproduction printed {'user': None, 'agent': None} / 0; after the fix it prints both source sections / 1.
  • Coverage and gaps: the 4 failures above are all [sqlite]-arm tests in
    test_todo_projection_recovery.py and test_task_graph_topology.py. They fail
    in 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-qualification
    gate 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_plane suite was not completed: it exceeds a usable runtime in
    this environment. Untested paths: none identified beyond that.

Frontend / Visual Evidence

  • UI impact: none

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

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: main

  • Direction tracker or promotion unit: N/A

Shared-authority RFC fixture impact

N/A

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

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 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.

动机

objective 是用户散文,但两个文档生成入口(bootstrap.render_state_markdownprojects/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_sectiontodos/machine_region.visible_markdown_lines 增加 in_objective 分支,把 region 内的行整段跳过,于是 objective 的围栏与注释不再被当作文档结构,真实围栏仍按原规则解析。

具体改动

  • loopx/control_plane/goals/active_state_metadata.py(+42):新增 region 常量、render_objective_blockread_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。

关键代码讲解

  1. render_objective_blockactive_state_metadata.py:40):输出 <!-- loopx:objective-v0 begin -->\n{原文}\n<!-- loopx:objective-v0 end -->。注意原文没有任何引用或转义,隔离完全依赖读者认识这对标记。
  2. visible_markdown_linesmachine_region.py:57):进入 region 后整段 continue,直到出现 END 标记;因此识别该 region 的读取器(LoopX 自己的 Todo 解析)确实不再被 objective 的围栏影响。
  3. 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=CONFLICTINGmergeStateStatus=DIRTY,并且远端没有任何 check runcheck-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.

@huangruiteng

Copy link
Copy Markdown
Collaborator

更早的 PR 4402 好像已经修了这个问题了

@huangruiteng

Copy link
Copy Markdown
Collaborator

Closing as superseded by #4402.

维护者此前指出的重复修复问题核对后,这个 PR 的问题定位和回归用例有价值,但当前没有需要独立合入的功能增量:

本次在上述 main 上运行 tests/control_plane/test_goal_objective_projection.pytests/cli_commands/test_project_registry.pytests/control_plane/test_todo_machine_region.py,结果 104 passed。另将本 PR 的六类输入(backtick/tilde 围栏、未闭合 HTML 注释、Todo 形态文本、普通文本、闭合多行围栏)分别通过两个现有生产 writer 验证,12/12 通过:Todo 段可读、示例文本不会成为任务、Objective 文本按现有展示契约读回。所有验证使用临时合成数据;这是当前 main 的定向验证,未重跑本 PR 或完整测试套件。

因此关闭这个重复实现,保留现有主干方案;不需要为相同修复再建后续任务。感谢提供复现及边界用例。

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.

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.

[Bug]: Fenced goal text hides generated Todo sections

2 participants