From fb20c153ba2c2a7376efe68091a76ca470effbde Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:25:52 +0800 Subject: [PATCH 1/3] fix(heartbeat): preserve the selected registry in host commands Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/cli_commands/support_control.py | 3 + loopx/control_plane/heartbeat/budget.py | 17 ++- loopx/control_plane/heartbeat/builder.py | 17 ++- .../work_items/action_selection_contract.py | 7 +- .../work_items/interaction_contract.py | 11 +- loopx/project_prompt.py | 18 ++- .../test_heartbeat_registry_route.py | 122 ++++++++++++++++++ 7 files changed, 182 insertions(+), 13 deletions(-) create mode 100644 tests/control_plane/test_heartbeat_registry_route.py diff --git a/loopx/cli_commands/support_control.py b/loopx/cli_commands/support_control.py index dd6bf3a800..68efa11d48 100644 --- a/loopx/cli_commands/support_control.py +++ b/loopx/cli_commands/support_control.py @@ -278,6 +278,7 @@ def handle_support_control_command( active_state_source = None registered_agents = None effective_agent_id = args.agent_id + agent_registry_path = registry_path requested_runtime_profile = ( SchedulerRuntimeProfile.CODEX_APP_HEARTBEAT.value if args.codex_app @@ -368,6 +369,7 @@ def handle_support_control_command( thin=bool(args.thin), cli_bin=args.cli_bin, runtime_root=args.runtime_root, + registry_path=agent_registry_path.resolve(), agent_id=effective_agent_id, agent_scopes=args.agent_scopes, agent_profile=agent_profile, @@ -429,6 +431,7 @@ def handle_support_control_command( thin=bool(args.thin), cli_bin=args.cli_bin, runtime_root=args.runtime_root, + registry_path=agent_registry_path.resolve(), agent_id=effective_agent_id or args.agent_id, agent_scopes=args.agent_scopes, registered_agents=registered_agents, diff --git a/loopx/control_plane/heartbeat/budget.py b/loopx/control_plane/heartbeat/budget.py index 74ad38f024..947d17db0a 100644 --- a/loopx/control_plane/heartbeat/budget.py +++ b/loopx/control_plane/heartbeat/budget.py @@ -42,8 +42,16 @@ def heartbeat_prompt_mode( return "thin" -def prompt_budget_text(text: str, *, goal_id: str, active_state: str) -> str: - return text.replace(goal_id, "").replace(active_state, "") +def prompt_budget_text( + text: str, *, goal_id: str, active_state: str, + registry_path: str | None = None, +) -> str: + normalized = text.replace(goal_id, "").replace(active_state, "") + # Compare authored guidance independently of the host's selected path length. + # char_count below still reports the actual transport/body size. + if registry_path: + normalized = normalized.replace(registry_path, "") + return normalized def build_interface_budget( @@ -51,6 +59,7 @@ def build_interface_budget( task_body: str, goal_id: str, active_state: str, + registry_path: str | None = None, full: bool = False, compact: bool = False, brief: bool = False, @@ -62,7 +71,9 @@ def build_interface_budget( if native_goal_host else heartbeat_prompt_mode(full=full, compact=compact, brief=brief, thin=thin) ) - budget_text = prompt_budget_text(task_body, goal_id=goal_id, active_state=active_state) + budget_text = prompt_budget_text( + task_body, goal_id=goal_id, active_state=active_state, registry_path=registry_path, + ) budget_chars = len(budget_text) reward_memory_headroom = ( REWARD_MEMORY_OUTCOME_PROMPT_HEADROOM_CHARS diff --git a/loopx/control_plane/heartbeat/builder.py b/loopx/control_plane/heartbeat/builder.py index 79c34b48b4..029636d238 100644 --- a/loopx/control_plane/heartbeat/builder.py +++ b/loopx/control_plane/heartbeat/builder.py @@ -134,6 +134,7 @@ def _heartbeat_regeneration_commands( *, cli_bin: str, runtime_root: str | Path | None, + registry_path: str | Path | None, goal_id: str, active_state_arg: str, agent_args: str, @@ -148,6 +149,7 @@ def _heartbeat_regeneration_commands( command_prefix = render_cli_command_prefix( cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, ) return tuple( f"{command_prefix} heartbeat-prompt --{mode}{suffix}" @@ -160,6 +162,7 @@ def _heartbeat_prompt_commands( goal_id: str, cli_bin: str, runtime_root: str | Path | None, + registry_path: str | Path | None, normalized_agent_id: str | None, normalized_available_capabilities: tuple[str, ...], task_body_available_capabilities: tuple[str, ...], @@ -177,6 +180,7 @@ def _heartbeat_prompt_commands( goal_id, cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, agent_id=normalized_agent_id, available_capabilities=normalized_available_capabilities, runtime_profile=runtime_profile, @@ -192,6 +196,7 @@ def _heartbeat_prompt_commands( ), cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, agent_id=normalized_agent_id, available_capabilities=normalized_available_capabilities, ) @@ -202,6 +207,7 @@ def _heartbeat_prompt_commands( goal_id, cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, agent_id=normalized_agent_id, available_capabilities=task_body_available_capabilities, runtime_profile=runtime_profile, @@ -212,6 +218,7 @@ def _heartbeat_prompt_commands( source=VISIBLE_GOAL_SLOT_SPEND_SOURCE, cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, agent_id=normalized_agent_id, ) scheduler_args = render_scheduler_execution_args( @@ -226,6 +233,7 @@ def _heartbeat_prompt_commands( ) = _heartbeat_regeneration_commands( cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, goal_id=goal_id, active_state_arg=active_state_arg, agent_args=agent_args, @@ -234,7 +242,7 @@ def _heartbeat_prompt_commands( turn_identity_arg=turn_identity_arg, ) pr_review_pre_quota_command = ( - f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root)} " + f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root, registry_path=registry_path)} " f"heartbeat-prequota -g {shlex.quote(goal_id)} " f"-a {shlex.quote(normalized_agent_id)}" if normalized_agent_id @@ -250,12 +258,14 @@ def _heartbeat_prompt_commands( goal_id, cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, agent_id=normalized_agent_id, ), "progress_refresh_state_command": render_accountable_progress_refresh_command( goal_id, cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, agent_id=normalized_agent_id, ), "pr_review_pre_quota_command": pr_review_pre_quota_command, @@ -286,6 +296,7 @@ def build_heartbeat_prompt( thin: bool = False, cli_bin: str = "loopx", runtime_root: str | Path | None = None, + registry_path: str | Path | None = None, agent_id: str | None = None, agent_scopes: list[str] | tuple[str, ...] | None = None, agent_profile: dict[str, Any] | None = None, @@ -434,6 +445,7 @@ def build_heartbeat_prompt( goal_id=goal_id, cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, normalized_agent_id=normalized_agent_id, normalized_available_capabilities=normalized_available_capabilities, task_body_available_capabilities=task_body_available_capabilities, @@ -560,6 +572,7 @@ def build_heartbeat_prompt( task_body=task_body, goal_id=goal_id, active_state=active_state_text, + registry_path=str(registry_path) if registry_path is not None else None, full=full, compact=compact, brief=brief, @@ -605,6 +618,7 @@ def build_heartbeat_prompt_error_payload( thin: bool = False, cli_bin: str = "loopx", runtime_root: str | Path | None = None, + registry_path: str | Path | None = None, agent_id: str | None = None, agent_scopes: list[str] | tuple[str, ...] | None = None, registered_agents: list[str] | tuple[str, ...] | None = None, @@ -635,6 +649,7 @@ def build_heartbeat_prompt_error_payload( command_prefix = render_cli_command_prefix( cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, ) expanded_prompt_command = f"{command_prefix} heartbeat-prompt --full --goal-id {goal_id}{active_state_arg}{agent_args}{capability_args}" compact_prompt_command = f"{command_prefix} heartbeat-prompt --compact --goal-id {goal_id}{active_state_arg}{agent_args}{capability_args}" diff --git a/loopx/control_plane/work_items/action_selection_contract.py b/loopx/control_plane/work_items/action_selection_contract.py index cbcc5be008..3bb3d00fc8 100644 --- a/loopx/control_plane/work_items/action_selection_contract.py +++ b/loopx/control_plane/work_items/action_selection_contract.py @@ -34,6 +34,7 @@ def action_portfolio_selection_command_template( scheduler_args: str, turn_instance_id: str | None, runtime_root: str | None = None, + registry_path: str | None = None, ) -> str | None: if not action_portfolio_requires_explicit_selection(payload): return None @@ -45,9 +46,9 @@ def action_portfolio_selection_command_template( if turn_instance_id else "" ) - command_prefix = "loopx" - if runtime_root: - command_prefix += f" --runtime-root {shlex.quote(str(runtime_root))}" + command_prefix = render_cli_command_prefix( + runtime_root=runtime_root, registry_path=registry_path, + ) return ( f"{command_prefix} --format json quota should-run" f" --goal-id {shlex.quote(goal_id)}" diff --git a/loopx/control_plane/work_items/interaction_contract.py b/loopx/control_plane/work_items/interaction_contract.py index af926aa8e6..269045bbf4 100644 --- a/loopx/control_plane/work_items/interaction_contract.py +++ b/loopx/control_plane/work_items/interaction_contract.py @@ -579,6 +579,7 @@ def _turn_scoped_cli_settlement_context( ), turn_instance_id: str | None = None, runtime_root: str | None = None, + registry_path: str | None = None, ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: if selection.action_portfolio_requires_explicit_selection(payload): return None, None @@ -622,7 +623,7 @@ def _turn_scoped_cli_settlement_context( ), goal_id=goal_id, agent_id=agent_id, - command_prefix=selection.render_cli_command_prefix(runtime_root=runtime_root), + command_prefix=selection.render_cli_command_prefix(runtime_root=runtime_root, registry_path=registry_path), todo_id=todo_id, replan_obligation_id=replan_obligation_id, scoped_cli_args=scoped_cli_args, @@ -696,11 +697,13 @@ def _selection_recovery_command( payload: dict[str, Any], *, available_capabilities: Any, scheduler_execution_context: Mapping[str, Any] | SchedulerExecutionContextResolution | None, turn_instance_id: str | None, runtime_root: str | None, + registry_path: str | None = None, ) -> str: identity = payload.get("agent_identity") if isinstance(payload.get("agent_identity"), dict) else {} return selection.action_selection_recovery_command( goal_id=str(payload.get("goal_id") or ""), agent_id=identity.get("agent_id"), runtime_root=runtime_root, + registry_path=registry_path, turn_instance_id=turn_instance_id, available_capabilities=available_capabilities, scheduler_args=render_scheduler_execution_args(scheduler_execution_context=scheduler_execution_context), ) + _goal_ref_cli_arg(payload) @@ -740,6 +743,7 @@ def interaction_next_cli_actions( payload, available_capabilities=available_capabilities, scheduler_execution_context=scheduler_execution_context, turn_instance_id=turn_instance_id, runtime_root=runtime_root, + registry_path=registry_path, )] goal_id = str(payload.get("goal_id") or "") command_prefix = selection.render_cli_command_prefix(runtime_root=runtime_root, registry_path=registry_path) @@ -760,6 +764,7 @@ def interaction_next_cli_actions( scheduler_execution_context=scheduler_execution_context, turn_instance_id=turn_instance_id, runtime_root=runtime_root, + registry_path=registry_path, ) settlement_args = settlement_binding_args(settlement_plan) try: @@ -774,6 +779,7 @@ def interaction_next_cli_actions( scheduler_args=scheduler_args, turn_instance_id=turn_instance_id, runtime_root=runtime_root, + registry_path=registry_path, ) if selection_command_template: return [selection_command_template + _goal_ref_cli_arg(payload)] @@ -1316,6 +1322,7 @@ def _build_interaction_cli_channel( payload, available_capabilities=available_capabilities, scheduler_execution_context=scheduler_execution_context, turn_instance_id=turn_instance_id, runtime_root=runtime_root, + registry_path=registry_path, )) spend_after_selection = selection.delivery_spend_allowed(payload, spend_after_validation) settlement_plan, replan_settlement_contract = ( @@ -1325,6 +1332,7 @@ def _build_interaction_cli_channel( scheduler_execution_context=scheduler_execution_context, turn_instance_id=turn_instance_id, runtime_root=runtime_root, + registry_path=registry_path, ) ) channel = { @@ -1682,6 +1690,7 @@ def build_interaction_contract( payload, available_capabilities=available_capabilities, scheduler_execution_context=scheduler_execution_context, turn_instance_id=turn_instance_id, runtime_root=runtime_root, + registry_path=registry_path, ), } else: diff --git a/loopx/project_prompt.py b/loopx/project_prompt.py index b7ce0f74a7..fe70c0486c 100644 --- a/loopx/project_prompt.py +++ b/loopx/project_prompt.py @@ -48,10 +48,13 @@ def render_cli_command_prefix( *, cli_bin: str = "loopx", runtime_root: str | Path | None = None, + registry_path: str | Path | None = None, ) -> str: prefix = shell_arg(cli_bin) if runtime_root is not None: prefix += f" --runtime-root {shell_arg(str(runtime_root))}" + if registry_path is not None: + prefix += f" --registry {shell_arg(str(registry_path))}" return prefix @@ -169,6 +172,7 @@ def render_quota_guard_command( *, cli_bin: str = "loopx", runtime_root: str | Path | None = None, + registry_path: str | Path | None = None, agent_id: str | None = None, available_capabilities: Any = None, runtime_profile: str | None = None, @@ -205,10 +209,10 @@ def render_quota_guard_command( else: turn_arg = "" registry_arg = ( - _render_global_registry_arg(runtime_root) if include_shared_registry else "" + _render_global_registry_arg(runtime_root) if include_shared_registry and registry_path is None else "" ) return ( - f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root)} --format json " + f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root, registry_path=registry_path)} --format json " f"{registry_arg}" f"quota should-run --goal-id {shell_arg(goal_id)}{agent_arg}" f"{capability_args}{scheduler_args}{turn_arg}" @@ -221,14 +225,15 @@ def render_quota_spend_command( source: str = "adapter", cli_bin: str = "loopx", runtime_root: str | Path | None = None, + registry_path: str | Path | None = None, agent_id: str | None = None, available_capabilities: Any = None, ) -> str: agent_arg = f" --agent-id {shell_arg(agent_id)}" if agent_id else "" capability_args = render_available_capability_args(available_capabilities) return ( - f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root)} --format json " - f"{_render_global_registry_arg(runtime_root)}" + f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root, registry_path=registry_path)} --format json " + f"{_render_global_registry_arg(runtime_root) if registry_path is None else ''}" "quota spend-slot " f"--goal-id {shell_arg(goal_id)} " f"--slots 1 --source {shell_arg(source)} --execute{agent_arg}{capability_args}" @@ -240,6 +245,7 @@ def render_refresh_state_command( *, cli_bin: str = "loopx", runtime_root: str | Path | None = None, + registry_path: str | Path | None = None, project: str | None = None, agent_id: str | None = None, progress_scope: str | None = None, @@ -266,7 +272,7 @@ def render_refresh_state_command( else "" ) return ( - f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root)} " + f"{render_cli_command_prefix(cli_bin=cli_bin, runtime_root=runtime_root, registry_path=registry_path)} " f"refresh-state --goal-id {shell_arg(goal_id)}" f"{project_arg}{classification_arg}{scale_arg}{outcome_arg}{agent_arg}{scope_arg}" ) @@ -277,6 +283,7 @@ def render_accountable_progress_refresh_command( *, cli_bin: str = "loopx", runtime_root: str | Path | None = None, + registry_path: str | Path | None = None, agent_id: str | None = None, progress_scope: str | None = None, classification: str = "", @@ -287,6 +294,7 @@ def render_accountable_progress_refresh_command( goal_id, cli_bin=cli_bin, runtime_root=runtime_root, + registry_path=registry_path, agent_id=agent_id, progress_scope=progress_scope, classification=classification, diff --git a/tests/control_plane/test_heartbeat_registry_route.py b/tests/control_plane/test_heartbeat_registry_route.py new file mode 100644 index 0000000000..bb288f6a76 --- /dev/null +++ b/tests/control_plane/test_heartbeat_registry_route.py @@ -0,0 +1,122 @@ +"""Generated host commands keep the selected authority, including from another cwd.""" +from __future__ import annotations + +import json +import os +import shlex +import subprocess +import sys +from pathlib import Path + +import pytest +import test_quota_settlement_cli as cli +import test_quota_authority_settlement_journey as journey + +from loopx.heartbeat_prompt import build_heartbeat_prompt, build_heartbeat_prompt_error_payload + + +def _route(command: str, registry: Path, runtime: Path) -> None: + argv = shlex.split(command) + assert argv.count("--registry") == 1 + assert argv[argv.index("--registry") + 1] == str(registry) + assert argv[argv.index("--runtime-root") + 1] == str(runtime) + + +@pytest.mark.parametrize("mode", ["full", "compact", "brief", "thin"]) +def test_prompt_and_recovery_commands_keep_explicit_route(tmp_path, mode): + registry = tmp_path / "selected authority" / "registry.json" + runtime = tmp_path / "selected runtime" + payload = build_heartbeat_prompt( + goal_id="route-fixture", registry_path=registry, runtime_root=runtime, + cli_bin="/fixture tools/loopx", agent_id="worker-a", registered_agents=["worker-a"], + available_capabilities=["shell", "external_evidence_poll"], + runtime_profile="codex_app_heartbeat", **{mode: True}, + ) + for key in ("quota_guard_command", "quota_spend_command", "refresh_state_command", + "progress_refresh_state_command", "pr_review_pre_quota_command", + "expanded_prompt_command", "compact_prompt_command", + "brief_prompt_command", "thin_prompt_command"): + if key not in payload: + continue # thin intentionally omits redundant regeneration metadata + _route(payload[key], registry, runtime) + assert shlex.split(payload[key])[0] == "/fixture tools/loopx" + assert '--turn-instance-id "${LOOPX_TURN:?}"' in payload["quota_guard_command"] + error = build_heartbeat_prompt_error_payload( + goal_id="route-fixture", error="synthetic missing registration", + registry_path=registry, runtime_root=runtime, + ) + assert error["ok"] is False + assert error["quota_guard_command"] is None + for key in ("expanded_prompt_command", "compact_prompt_command", + "brief_prompt_command", "thin_prompt_command"): + _route(error[key], registry, runtime) + + +def _execute(argv: list[str], cwd: Path): + env = dict(os.environ, PYTHONPATH=str(cli.REPO_ROOT)) + result = subprocess.run([sys.executable, "-m", "loopx.cli", "--format", "json", *argv], + cwd=cwd, env=env, text=True, capture_output=True, check=False) + return result.returncode, json.loads(result.stdout) + + +def test_cli_generated_guard_selection_and_settlement_ignore_conflicting_cwd_registry(tmp_path): + project, runtime, original, _, _ = journey._source(tmp_path, provider="file") + registry = tmp_path / "selected authority" / "registry.json" + registry.parent.mkdir() + registry.write_text(original.read_text()) + conflict = json.loads(original.read_text()) + conflict["goals"][0]["coordination"]["registered_agents"] = ["another-worker"] + original.write_text(json.dumps(conflict)) + code, prompt = _execute([ + "--registry", str(registry), "--runtime-root", str(runtime), "heartbeat-prompt", + "--goal-id", cli.GOAL_ID, "--agent-id", cli.AGENT_ID, "--codex-app", "--full", + "--turn-instance-id", cli.TURN_ID, + ], project) + assert code == 0, prompt + _route(prompt["quota_guard_command"], registry, runtime) + guard_argv = shlex.split(prompt["quota_guard_command"])[1:] + guard_argv = [cli.TURN_ID if arg == "${LOOPX_TURN:?}" else arg for arg in guard_argv] + # No test harness adds the registry/runtime or a missing Turn identity. + code, unbound = _execute(guard_argv, project) + assert code == 0, unbound + channel = unbound["interaction_contract"]["cli_channel"] + selection = (channel["selection_command"]["route_prefix"] + " " + + channel["selection_command"]["command_args_template"]) + _route(selection, registry, runtime) + # Rejected selection's reentry also keeps the same route and Turn. + rejected_argv = guard_argv + ["--todo-id", "todo_absent"] + code, rejected = _execute(rejected_argv, project) + assert code != 0 or rejected["interaction_contract"]["agent_channel"]["delivery_allowed"] is False + recovery = rejected["interaction_contract"]["agent_channel"]["primary_action"] + _route(recovery, registry, runtime) + assert cli.TURN_ID in recovery + + code, guard = _execute(shlex.split(selection.replace("{todo_id}", cli.TODO_ID))[1:], project) + assert code == 0, guard + assert guard["selected_todo"]["todo_id"] == cli.TODO_ID + channel = guard["interaction_contract"]["cli_channel"] + for command in channel["next_cli_actions"]: + if command.startswith("loopx "): + _route(command, registry, runtime) + plan = channel["settlement_plan"] + for step in plan["ordered_steps"]: + if isinstance(step, dict) and isinstance(step.get("command_template"), str): + _route(step["command_template"], registry, runtime) + assert cli._spend_run_count(runtime) == 0 + + +def test_invalid_selected_registry_does_not_fall_back_to_valid_local_roster(tmp_path): + project, runtime, original = cli._write_fixture(tmp_path) + registry = tmp_path / "selected authority" / "registry.json" + registry.parent.mkdir() + selected = json.loads(original.read_text()) + selected["goals"][0]["coordination"]["registered_agents"] = ["another-worker"] + registry.write_text(json.dumps(selected)) + code, error = _execute([ + "--registry", str(registry), "--runtime-root", str(runtime), "heartbeat-prompt", + "--goal-id", cli.GOAL_ID, "--agent-id", cli.AGENT_ID, "--codex-app", "--full", + ], project) + assert code != 0 + assert error["ok"] is False + assert error["quota_guard_command"] is None + _route(error["thin_prompt_command"], registry, runtime) From bf09a6b828de4380b8e9197bde0b211925c8f516 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:27:30 +0800 Subject: [PATCH 2/3] docs(heartbeat): record routing scope and refresh I/O coordinates Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- docs/architecture/rfcs/loopx-overall-roadmap-v0.md | 5 +++++ loopx/semantics/project_registry_io_manifest_v1.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/architecture/rfcs/loopx-overall-roadmap-v0.md b/docs/architecture/rfcs/loopx-overall-roadmap-v0.md index 1528d2c16f..32c8155ef1 100644 --- a/docs/architecture/rfcs/loopx-overall-roadmap-v0.md +++ b/docs/architecture/rfcs/loopx-overall-roadmap-v0.md @@ -902,6 +902,11 @@ outer Turn and native Goal driver on one binding. - **Final acceptance:** users submit and revise a macro-goal through both entries; heterogeneous local/cloud workers deliver dependent artifacts continuously. The manager detects blockers, replans within grants, reports and closes the Goal through independent acceptance. No duplicate protected effects or stale/unauthorized commits; performance claims need real measurements. Run synthetic pressure before an explicitly authorized live cohort. This plan starts no hundred-Agent paid run. - **Rollback:** reduce concurrency/admission while retaining registrations, history and reconciliation; never weaken correctness gates. +Heartbeat command guidance preserves the selected registry through guard, action +selection, recovery and settlement, independently of the caller's working +directory and runtime root. Conflicting-registry CLI fixtures qualify this +bounded routing repair; they do not qualify host latency or fleet scale. + ## 7. Execution and Review Contract Select one complete, verifiable slice whose prerequisites hold. Check latest main and canonical Todos first; qualify existing work instead of rebuilding it, update existing tasks and retain supersession lineage. These cards are a static roadmap. Actual assignees, status, PRs and blockers belong in LoopX Todos. diff --git a/loopx/semantics/project_registry_io_manifest_v1.json b/loopx/semantics/project_registry_io_manifest_v1.json index bbafba64ba..8d9f3c8af1 100644 --- a/loopx/semantics/project_registry_io_manifest_v1.json +++ b/loopx/semantics/project_registry_io_manifest_v1.json @@ -2079,7 +2079,7 @@ }, { "site": "loopx/project_prompt.py::.build_new_project_prompt::codec_read:load_registry#1", - "line": 427, + "line": 435, "column": 9, "kind": "codec_read", "api": "load_registry", From ee26083a60852da652e8cd1def6eaba2a3a2e672 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Wed, 7 Oct 2026 05:44:47 +0800 Subject: [PATCH 3/3] fix(heartbeat): normalize escaped registry paths in guidance budget Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/control_plane/heartbeat/budget.py | 6 ++++-- .../control_plane/test_heartbeat_registry_route.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/loopx/control_plane/heartbeat/budget.py b/loopx/control_plane/heartbeat/budget.py index 947d17db0a..68652afa8c 100644 --- a/loopx/control_plane/heartbeat/budget.py +++ b/loopx/control_plane/heartbeat/budget.py @@ -2,6 +2,7 @@ from __future__ import annotations +import shlex from typing import Any @@ -46,12 +47,13 @@ def prompt_budget_text( text: str, *, goal_id: str, active_state: str, registry_path: str | None = None, ) -> str: - normalized = text.replace(goal_id, "").replace(active_state, "") + normalized = text # Compare authored guidance independently of the host's selected path length. # char_count below still reports the actual transport/body size. if registry_path: + normalized = normalized.replace(shlex.quote(registry_path), "") normalized = normalized.replace(registry_path, "") - return normalized + return normalized.replace(goal_id, "").replace(active_state, "") def build_interface_budget( diff --git a/tests/control_plane/test_heartbeat_registry_route.py b/tests/control_plane/test_heartbeat_registry_route.py index bb288f6a76..b7d1aa3b7a 100644 --- a/tests/control_plane/test_heartbeat_registry_route.py +++ b/tests/control_plane/test_heartbeat_registry_route.py @@ -13,6 +13,7 @@ import test_quota_authority_settlement_journey as journey from loopx.heartbeat_prompt import build_heartbeat_prompt, build_heartbeat_prompt_error_payload +from loopx.control_plane.heartbeat.budget import build_interface_budget def _route(command: str, registry: Path, runtime: Path) -> None: @@ -22,6 +23,19 @@ def _route(command: str, registry: Path, runtime: Path) -> None: assert argv[argv.index("--runtime-root") + 1] == str(runtime) +def test_budget_normalizes_shell_quoted_registry_before_goal_substrings(): + registry = "/fixture/route-fixture's authority/" + "long-directory/" * 300 + "registry.json" + body = "loopx --registry " + shlex.quote(registry) + " quota should-run --goal-id route-fixture" + budget = build_interface_budget( + task_body=body, goal_id="route-fixture", active_state="/fixture/state.md", + registry_path=registry, thin=True, + ) + assert budget["char_count"] == len(body) > budget["max_chars"] + expected = "loopx --registry quota should-run --goal-id " + assert budget["budget_char_count"] == len(expected) + assert budget["within_budget"] is True + + @pytest.mark.parametrize("mode", ["full", "compact", "brief", "thin"]) def test_prompt_and_recovery_commands_keep_explicit_route(tmp_path, mode): registry = tmp_path / "selected authority" / "registry.json"