From 965e2aae629b41a17e97708683d3b7051c8506c4 Mon Sep 17 00:00:00 2001 From: Hao Zhe Date: Tue, 15 Sep 2026 03:13:19 +0800 Subject: [PATCH 1/4] fix(goals): isolate objective Markdown from Todo state Keep objective examples from hiding or creating generated Todos while preserving legacy registration and chat readback. Signed-off-by: Hao Zhe Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../active-state-structured-projection-v0.md | 9 +++ loopx/bootstrap.py | 7 +- loopx/chat_server.py | 19 +++-- loopx/control_plane/projects/registry.py | 19 ++++- loopx/presentation/markdown.py | 13 ++++ tests/cli_commands/test_project_registry.py | 28 ++++++- .../test_todo_next_action_settlement.py | 76 +++++++++++++++++++ 7 files changed, 157 insertions(+), 14 deletions(-) diff --git a/docs/reference/protocols/active-state-structured-projection-v0.md b/docs/reference/protocols/active-state-structured-projection-v0.md index b213aecec8..62f7d0dbe2 100644 --- a/docs/reference/protocols/active-state-structured-projection-v0.md +++ b/docs/reference/protocols/active-state-structured-projection-v0.md @@ -119,6 +119,15 @@ Directly editing a projection is not a state transition. ## Markdown Ownership Boundary +New bootstrap and project-registration documents quote each Objective line and +escape HTML metacharacters. Fences, comments, headings, and Todo markers in the +objective therefore remain content rather than document structure. The +frontmatter stores the objective as a JSON string with Unicode line separators +escaped. Chat context readback removes the generated quotation and decodes the +text. Existing project registrations remain idempotent without rewriting their +state. This changes objective presentation, not Todo authority or transitions; +existing malformed documents are not automatically repaired. + Markdown is not one undifferentiated database row. Agents generate and maintain both its structured sections and narrative through LoopX. The distinction is canonical ownership, not human versus Agent authorship: after promotion, diff --git a/loopx/bootstrap.py b/loopx/bootstrap.py index 4138655e83..b694c466c4 100644 --- a/loopx/bootstrap.py +++ b/loopx/bootstrap.py @@ -40,6 +40,7 @@ MULTI_SUBAGENT_ORCHESTRATION_MODE, ) from .paths import rel_or_abs, resolve_runtime_root +from .presentation.markdown import markdown_blockquote, markdown_frontmatter_string from .registry_writability import probe_registry_write_path from .todos import add_todo_to_lines @@ -481,7 +482,7 @@ def render_state_markdown( include_connection_validation: bool = True, handoff_mode: str = HANDOFF_MODE_LEGACY, ) -> str: - safe_objective = objective.replace('"', '\\"') + safe_objective = markdown_frontmatter_string(objective) profile_summary = execution_profile_summary(execution_profile) onboarding_markdown = render_onboarding_state_markdown( onboarding_scan=onboarding_scan, @@ -507,7 +508,7 @@ def render_state_markdown( state_text = f"""--- status: active owner_mode: goal -objective: "{safe_objective}" +objective: {safe_objective} updated_at: {updated_at} adapter_id: {goal_id} {handoff_mode_line}--- @@ -516,7 +517,7 @@ def render_state_markdown( ## Objective -{objective} +{markdown_blockquote(objective)} ## Authority Sources diff --git a/loopx/chat_server.py b/loopx/chat_server.py index cfa13adf94..9047ed5261 100644 --- a/loopx/chat_server.py +++ b/loopx/chat_server.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +from html import unescape import mimetypes import time import uuid @@ -124,16 +125,20 @@ def _compact_text(value: Any, *, limit: int = 600) -> str: def _active_state_section(state_text: str, heading: str) -> str: - marker = f"## {heading}" - start = state_text.find(marker) - if start < 0: + lines = state_text.splitlines() + try: + start = next(i for i, line in enumerate(lines) if line.rstrip(" \t") == f"## {heading}") + 1 + except StopIteration: return "" - content_start = start + len(marker) - end = state_text.find("\n## ", content_start) - section = state_text[content_start : end if end >= 0 else None] + end = next((i for i in range(start, len(lines)) if lines[i].startswith("## ")), len(lines)) + section_lines = [line for line in lines[start:end] if line] + if heading == "Objective" and section_lines and all( + line.startswith("> ") for line in section_lines + ): + return _compact_text(" ".join(unescape(line[2:]) for line in section_lines)) lines = [ line.strip().removeprefix("- ").strip() - for line in section.splitlines() + for line in section_lines if line.strip() and not line.lstrip().startswith("", + id="todo-example", + ), + pytest.param( + "```markdown\n## Agent Todo\n" + "\n" + "- [ ] Example only.\n" + "\n```", + id="fenced-region-example", + ), + ], +) +def test_bootstrap_keeps_objective_separate_from_todo_sources( + tmp_path: Path, objective: str, +) -> None: + state_text = render_state_markdown( + project=tmp_path, + goal_id=GOAL_ID, + adapter_kind="read_only_project_map_v0", + objective=objective, + updated_at="2026-08-21T00:00:00+08:00", + goal_doc=None, + execution_profile=None, + ) + + items, archive, sources = parse_todo_source(state_text) + + assert sources == { + "user": "User Todo / Owner Review Reading Queue", + "agent": "Agent Todo", + } + assert items["user"] == [] + assert archive == [] + assert len(items["agent"]) == 1 + assert items["agent"][0]["action_kind"] == "onboarding_connection_validation" + assert active_state_next_action_entries(state_text) == [items["agent"][0]["text"]] + assert _active_state_section(state_text, "Objective") == " ".join(objective.split()) + objective_line = next(line for line in state_text.splitlines() if line.startswith("objective: ")) + assert json.loads(objective_line.removeprefix("objective: ")) == objective + + +@pytest.mark.parametrize("heading", ["## Objective", "## Objective ", "## Objective\t"]) +def test_objective_readback_preserves_existing_heading_whitespace(heading: str) -> None: + state = f"{heading}\n\nKeep the original objective.\n\n## Next Action\n\nContinue." + assert _active_state_section(state, "Objective") == "Keep the original objective." + assert _active_state_section(state, "Missing") == "" + + def test_higher_priority_agent_todo_rebinds_generated_onboarding_next_action( tmp_path: Path, ) -> None: From 4bd5c0c545e8ed91b16b9364492e34216cca2cdb Mon Sep 17 00:00:00 2001 From: Hao Zhe Date: Tue, 15 Sep 2026 03:58:06 +0800 Subject: [PATCH 2/4] fix(goals): keep objective text within state ownership Move objective serialization and section readback to the existing state metadata owner, restoring dependency boundaries and the chat module budget without changing chat truncation. Signed-off-by: Hao Zhe Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- loopx/bootstrap.py | 2 +- loopx/chat_server.py | 24 +----------- .../goals/active_state_metadata.py | 37 +++++++++++++++++++ loopx/control_plane/projects/registry.py | 2 +- loopx/presentation/markdown.py | 13 ------- tests/cli_commands/test_project_registry.py | 4 +- .../test_todo_next_action_settlement.py | 8 ++-- 7 files changed, 47 insertions(+), 43 deletions(-) diff --git a/loopx/bootstrap.py b/loopx/bootstrap.py index b694c466c4..4085fe36e6 100644 --- a/loopx/bootstrap.py +++ b/loopx/bootstrap.py @@ -40,7 +40,7 @@ MULTI_SUBAGENT_ORCHESTRATION_MODE, ) from .paths import rel_or_abs, resolve_runtime_root -from .presentation.markdown import markdown_blockquote, markdown_frontmatter_string +from .control_plane.goals.active_state_metadata import markdown_blockquote, markdown_frontmatter_string from .registry_writability import probe_registry_write_path from .todos import add_todo_to_lines diff --git a/loopx/chat_server.py b/loopx/chat_server.py index 9047ed5261..b7567e28bd 100644 --- a/loopx/chat_server.py +++ b/loopx/chat_server.py @@ -1,7 +1,6 @@ from __future__ import annotations import json -from html import unescape import mimetypes import time import uuid @@ -39,6 +38,7 @@ from .chat_store import ChatSessionStore from .capabilities.manager_runtime import manager_runtime_capability_projection from .capabilities.manager_context.roundtrip import project_chat_session_snapshot +from .control_plane.goals.active_state_metadata import active_state_section_text from .control_plane.status.ssh_host_catalog import ( SSH_HOST_CATALOG_PATH, ssh_host_catalog_payload, @@ -124,26 +124,6 @@ def _compact_text(value: Any, *, limit: int = 600) -> str: return " ".join(str(value or "").split())[:limit].strip() -def _active_state_section(state_text: str, heading: str) -> str: - lines = state_text.splitlines() - try: - start = next(i for i, line in enumerate(lines) if line.rstrip(" \t") == f"## {heading}") + 1 - except StopIteration: - return "" - end = next((i for i in range(start, len(lines)) if lines[i].startswith("## ")), len(lines)) - section_lines = [line for line in lines[start:end] if line] - if heading == "Objective" and section_lines and all( - line.startswith("> ") for line in section_lines - ): - return _compact_text(" ".join(unescape(line[2:]) for line in section_lines)) - lines = [ - line.strip().removeprefix("- ").strip() - for line in section_lines - if line.strip() and not line.lstrip().startswith("", + id="todo-example", + ), + pytest.param( + "```markdown\n## Agent Todo\n" + "\n" + "- [ ] Example only.\n" + "\n```", + id="fenced-region-example", + ), + ], +) +def test_bootstrap_keeps_objective_separate_from_todo_sources( + tmp_path: Path, objective: str, +) -> None: + state_text = render_state_markdown( + project=tmp_path, + goal_id=GOAL_ID, + adapter_kind="read_only_project_map_v0", + objective=objective, + updated_at="2026-08-21T00:00:00+08:00", + goal_doc=None, + execution_profile=None, + ) + + items, archive, sources = parse_todo_source(state_text) + + assert sources == { + "user": "User Todo / Owner Review Reading Queue", + "agent": "Agent Todo", + } + assert items["user"] == [] + assert archive == [] + assert len(items["agent"]) == 1 + assert items["agent"][0]["action_kind"] == "onboarding_connection_validation" + assert active_state_next_action_entries(state_text) == [items["agent"][0]["text"]] + assert active_state_section_text(state_text, "Objective") == " ".join(objective.split()) + objective_line = next(line for line in state_text.splitlines() if line.startswith("objective: ")) + assert json.loads(objective_line.removeprefix("objective: ")) == objective + assert parse_state_frontmatter(state_text)["objective"] == objective + + +@pytest.mark.parametrize("heading", ["## Objective", "## Objective ", "## Objective\t"]) +def test_objective_readback_preserves_existing_heading_whitespace(heading: str) -> None: + state = f"{heading}\n\nKeep the original objective.\n\n## Next Action\n\nContinue." + assert active_state_section_text(state, "Objective") == "Keep the original objective." + assert active_state_section_text(state, "Missing") == "" + + + +@pytest.mark.parametrize("objective", ["```text\nImplement the task.\n```", "", - id="todo-example", - ), - pytest.param( - "```markdown\n## Agent Todo\n" - "\n" - "- [ ] Example only.\n" - "\n```", - id="fenced-region-example", - ), - ], -) -def test_bootstrap_keeps_objective_separate_from_todo_sources( - tmp_path: Path, objective: str, -) -> None: - state_text = render_state_markdown( - project=tmp_path, - goal_id=GOAL_ID, - adapter_kind="read_only_project_map_v0", - objective=objective, - updated_at="2026-08-21T00:00:00+08:00", - goal_doc=None, - execution_profile=None, - ) - - items, archive, sources = parse_todo_source(state_text) - - assert sources == { - "user": "User Todo / Owner Review Reading Queue", - "agent": "Agent Todo", - } - assert items["user"] == [] - assert archive == [] - assert len(items["agent"]) == 1 - assert items["agent"][0]["action_kind"] == "onboarding_connection_validation" - assert active_state_next_action_entries(state_text) == [items["agent"][0]["text"]] - assert active_state_section_text(state_text, "Objective") == " ".join(objective.split()) - objective_line = next(line for line in state_text.splitlines() if line.startswith("objective: ")) - assert json.loads(objective_line.removeprefix("objective: ")) == objective - - -@pytest.mark.parametrize("heading", ["## Objective", "## Objective ", "## Objective\t"]) -def test_objective_readback_preserves_existing_heading_whitespace(heading: str) -> None: - state = f"{heading}\n\nKeep the original objective.\n\n## Next Action\n\nContinue." - assert active_state_section_text(state, "Objective") == "Keep the original objective." - assert active_state_section_text(state, "Missing") == "" - - def test_higher_priority_agent_todo_rebinds_generated_onboarding_next_action( tmp_path: Path, ) -> None: diff --git a/tests/control_plane/test_todo_projection_recovery.py b/tests/control_plane/test_todo_projection_recovery.py index 7a6f76e678..73441e902d 100644 --- a/tests/control_plane/test_todo_projection_recovery.py +++ b/tests/control_plane/test_todo_projection_recovery.py @@ -325,3 +325,25 @@ def interrupted(_path): assert provider_projection.project_current_canonical_todos( registry_path=registry, runtime_root=runtime, goal_id="goal-a", )["status"] == "current" + + +@pytest.mark.parametrize("objective", ["```text Execute this example. ```", "## Agent Todo\n- [ ] Example only."]) +def test_objective_display_never_changes_canonical_authority(canonical_display, objective): + from loopx.bootstrap import render_state_markdown + from loopx.control_plane.goals.active_state_metadata import active_state_section_text + + registry, runtime, state = canonical_display + before = _read(runtime) + state.write_text(render_state_markdown( + project=state.parent, goal_id="goal-a", adapter_kind="read_only_project_map_v0", + objective=objective, updated_at="2026-09-15T00:00:00Z", + goal_doc=None, execution_profile=None, include_connection_validation=False, + )) + code, delivered = _run(registry, before["provider_revision"], "--execute") + assert code == 0 and delivered["status"] == "delivered", delivered + assert active_state_section_text(state.read_text(), "Objective") == " ".join(objective.split()) + assert _read(runtime) == before + state.write_text("