Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions loopx/cli_commands/support_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
19 changes: 16 additions & 3 deletions loopx/control_plane/heartbeat/budget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import shlex
from typing import Any


Expand Down Expand Up @@ -42,15 +43,25 @@ def heartbeat_prompt_mode(
return "thin"


def prompt_budget_text(text: str, *, goal_id: str, active_state: str) -> str:
return text.replace(goal_id, "<GOAL_ID>").replace(active_state, "<ACTIVE_STATE>")
def prompt_budget_text(
text: str, *, goal_id: str, active_state: str,
registry_path: str | None = None,
) -> str:
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), "<REGISTRY_PATH>")
normalized = normalized.replace(registry_path, "<REGISTRY_PATH>")
return normalized.replace(goal_id, "<GOAL_ID>").replace(active_state, "<ACTIVE_STATE>")


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,
Expand All @@ -62,7 +73,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
Expand Down
17 changes: 16 additions & 1 deletion loopx/control_plane/heartbeat/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}"
Expand All @@ -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, ...],
Expand All @@ -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,
Expand All @@ -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,
)
Expand All @@ -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,
Expand All @@ -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(
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}"
Expand Down
7 changes: 4 additions & 3 deletions loopx/control_plane/work_items/action_selection_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)}"
Expand Down
11 changes: 10 additions & 1 deletion loopx/control_plane/work_items/interaction_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 "<GOAL_ID>"),
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)
Expand Down Expand Up @@ -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 "<GOAL_ID>")
command_prefix = selection.render_cli_command_prefix(runtime_root=runtime_root, registry_path=registry_path)
Expand All @@ -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:
Expand All @@ -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)]
Expand Down Expand Up @@ -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 = (
Expand All @@ -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 = {
Expand Down Expand Up @@ -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:
Expand Down
18 changes: 13 additions & 5 deletions loopx/project_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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}"
Expand All @@ -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}"
Expand All @@ -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,
Expand All @@ -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}"
)
Expand All @@ -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 = "<PUBLIC_SAFE_PROGRESS_CLASSIFICATION>",
Expand All @@ -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,
Expand Down
Loading
Loading