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
1 change: 1 addition & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@ jobs:
tests/test_file_lock_cross_process.py
tests/test_host_thread_activity.py
tests/control_plane/test_effect_runtime_integration.py
tests/control_plane/test_turn_instance_arg.py
tests/control_plane/test_shadow_observation_retirement.py
tests/test_python_install_owner_platform.py
tests/test_self_update_runtime_activation.py
Expand Down
19 changes: 14 additions & 5 deletions loopx/control_plane/quota/effect_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"settlement_binding_args",
"settlement_result_payload",
"settlement_step_command",
"turn_instance_arg",
]


Expand All @@ -77,6 +78,18 @@ def _quoted_turn_ref(turn_instance_id_ref: str) -> str:
return shlex.quote(turn_instance_id_ref)


def turn_instance_arg(turn_instance_id: str | None = None) -> str:
"""Render the ``--turn-instance-id`` argument for the shell that runs it.

A caller holding a concrete Turn passes it through unchanged. Without one,
the argument names the reference the current host shell can expand, so a
native Windows host does not receive an empty Turn from POSIX syntax.
"""

reference = turn_instance_id or _default_turn_ref()
return f"--turn-instance-id {_quoted_turn_ref(reference)}"


def _settlement_actor_args(arguments: str, agent_id: str) -> str:
"""Identity owns the actor; optional host arguments cannot omit or retarget it."""
tokens = shlex.split(arguments)
Expand Down Expand Up @@ -302,8 +315,4 @@ def settlement_binding_args(plan: Mapping[str, Any] | None) -> str:
f" --goal-instance-id "
f"{shlex.quote(normalized_goal_ref['goal_instance_id'])}"
)
return (
binding_arg
+ f" --turn-instance-id {_quoted_turn_ref(turn_instance_id)}"
+ goal_ref_arg
)
return binding_arg + " " + turn_instance_arg(turn_instance_id) + goal_ref_arg
2 changes: 2 additions & 0 deletions loopx/control_plane/quota/settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
settlement_binding_args,
settlement_result_payload,
settlement_step_command,
turn_instance_arg,
)

QUOTA_SETTLEMENT_READBACK_REQUEST_SCHEMA = (
Expand Down Expand Up @@ -249,6 +250,7 @@ def render_settlement_progress_markdown(payload: dict[str, Any]) -> list[str]:
"settlement_binding_args",
"settlement_result_payload",
"settlement_step_command",
"turn_instance_arg",
]


Expand Down
3 changes: 2 additions & 1 deletion loopx/control_plane/work_items/interaction_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
SettlementStepKind,
settlement_binding_args,
settlement_step_command,
turn_instance_arg,
)
from ..quota.spend_sources import (
build_quota_spend_action,
Expand Down Expand Up @@ -697,7 +698,7 @@ def _admitted_interaction_next_cli_actions(
)
typed_heartbeat_receipt_retry = (
f"on missing/write_failed heartbeat_receipt only: {typed_quota_guard} "
'--turn-instance-id "${LOOPX_TURN:?}"'
f"{turn_instance_arg()}"
if scheduler_args
else (
"on missing/write_failed heartbeat_receipt only: retry the current "
Expand Down
17 changes: 15 additions & 2 deletions tests/control_plane/test_scheduler_execution_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from loopx.control_plane.todos.quota_summary import (
compact_quota_todo_summary_for_payload,
)
from loopx.control_plane.quota import effect_program as quota_effect_program
from loopx.control_plane.work_items.interaction_contract import (
finalize_user_gate_notification_cooldown,
interaction_next_cli_actions,
Expand Down Expand Up @@ -1626,7 +1627,19 @@ def test_generic_outer_controller_rerun_actions_are_typed() -> None:
assert all(" -H " not in action for action in actions)


def test_codex_app_monitor_quiet_retry_uses_turn_receipt() -> None:
@pytest.mark.parametrize(
("windows_host", "turn_arg"),
(
(False, '--turn-instance-id "${LOOPX_TURN:?}"'),
(True, "--turn-instance-id $LOOPX_TURN"),
),
)
def test_codex_app_monitor_quiet_retry_uses_turn_receipt(
monkeypatch: pytest.MonkeyPatch,
windows_host: bool,
turn_arg: str,
) -> None:
monkeypatch.setattr(quota_effect_program, "_is_windows_host", lambda: windows_host)
actions = interaction_next_cli_actions(
{
"goal_id": "codex-heartbeat-fixture",
Expand All @@ -1642,7 +1655,7 @@ def test_codex_app_monitor_quiet_retry_uses_turn_receipt() -> None:
(
"on missing/write_failed heartbeat_receipt only: loopx --format json "
"quota should-run --goal-id codex-heartbeat-fixture --agent-id "
'codex-fixture --codex-app --turn-instance-id "${LOOPX_TURN:?}"'
f"codex-fixture --codex-app {turn_arg}"
)
]

Expand Down
62 changes: 62 additions & 0 deletions tests/control_plane/test_turn_instance_arg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""The shared Turn argument must render for the shell that runs it.

Native Windows has no POSIX ``${NAME:?}`` expansion, so a command built there
has to name the PowerShell variable that carries the Turn. The same helper owns
the settlement binding arguments and the heartbeat-receipt retry hint, so both
inherit one rule instead of one copy each.
"""

from __future__ import annotations

import os

import pytest

from loopx.control_plane.quota import effect_program as quota_effect_program
from loopx.control_plane.quota.effect_program import turn_instance_arg

POSIX_REF = "${LOOPX_TURN:?}"
WINDOWS_REF = "$LOOPX_TURN"
POSIX_ARG = f'--turn-instance-id "{POSIX_REF}"'
WINDOWS_ARG = f"--turn-instance-id {WINDOWS_REF}"


def _host(monkeypatch: pytest.MonkeyPatch, windows: bool) -> None:
monkeypatch.setattr(quota_effect_program, "_is_windows_host", lambda: windows)


@pytest.mark.parametrize(
("windows", "expected"),
((False, POSIX_ARG), (True, WINDOWS_ARG)),
)
def test_a_host_default_reference_matches_the_running_shell(
monkeypatch: pytest.MonkeyPatch, windows: bool, expected: str
) -> None:
_host(monkeypatch, windows)

assert turn_instance_arg() == expected


@pytest.mark.parametrize("windows", [False, True])
def test_an_explicit_turn_is_quoted_unchanged(
monkeypatch: pytest.MonkeyPatch, windows: bool
) -> None:
_host(monkeypatch, windows)

assert turn_instance_arg("turn-settlement-host-1") == (
"--turn-instance-id turn-settlement-host-1"
)


def test_a_posix_reference_on_windows_names_the_powershell_variable(
monkeypatch: pytest.MonkeyPatch,
) -> None:
_host(monkeypatch, True)

assert turn_instance_arg(POSIX_REF) == WINDOWS_ARG


def test_the_default_matches_the_running_host_shell() -> None:
expected = WINDOWS_ARG if os.name == "nt" else POSIX_ARG

assert turn_instance_arg() == expected
Loading