Skip to content
Open
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
51 changes: 51 additions & 0 deletions loopx/control_plane/quota/spend_sources.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from ..scheduler.execution_context import (
NATIVE_GOAL_RUNTIME_PROFILES,
SchedulerExecutionContextResolution,
scheduler_runtime_profile_for_execution_context,
)
from ..todos.contract import normalize_todo_id
from .effect_program import SettlementStepKind, settlement_step_command


DEFAULT_SLOT_SPEND_SOURCE = "heartbeat"
VISIBLE_GOAL_SLOT_SPEND_SOURCE = "visible-goal"
Expand All @@ -9,3 +20,43 @@
"adapter",
VISIBLE_GOAL_SLOT_SPEND_SOURCE,
}


def quota_spend_source_for_execution_context(
value: Mapping[str, Any] | SchedulerExecutionContextResolution | None,
) -> str:
profile = scheduler_runtime_profile_for_execution_context(value)
if profile in NATIVE_GOAL_RUNTIME_PROFILES:
return VISIBLE_GOAL_SLOT_SPEND_SOURCE
return DEFAULT_SLOT_SPEND_SOURCE


def build_quota_spend_action(
goal_id: str,
*,
scoped_cli_args: str,
payload: Mapping[str, Any],
settlement_plan: Mapping[str, Any] | None,
scheduler_execution_context: (
Mapping[str, Any] | SchedulerExecutionContextResolution | None
) = None,
) -> str:
typed_command = settlement_step_command(
settlement_plan,
SettlementStepKind.QUOTA_SPEND,
)
if typed_command:
return typed_command
selected_value = payload.get("selected_todo")
selected = selected_value if isinstance(selected_value, Mapping) else {}
todo_id = normalize_todo_id(selected.get("todo_id"))
source = quota_spend_source_for_execution_context(scheduler_execution_context)
todo_arg = (
f" --todo-id {todo_id}"
if todo_id and source == DEFAULT_SLOT_SPEND_SOURCE
else ""
)
return (
f"loopx quota spend-slot --goal-id {goal_id} --slots 1 "
f"--source {source} --execute{todo_arg}{scoped_cli_args}"
)
83 changes: 18 additions & 65 deletions loopx/control_plane/work_items/interaction_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
settlement_binding_args,
settlement_step_command,
)
from ..quota.spend_sources import (
build_quota_spend_action,
)
from ..scheduler.execution_context import (
SchedulerExecutionContextResolution,
SchedulerRuntimeProfile,
Expand Down Expand Up @@ -615,39 +618,13 @@ def _codex_app_settlement_plan(
).as_dict()


def _quota_spend_action(
goal_id: str,
*,
scoped_cli_args: str,
payload: dict[str, Any],
settlement_plan: Mapping[str, Any] | None,
) -> str:
typed_command = settlement_step_command(
settlement_plan,
SettlementStepKind.QUOTA_SPEND,
)
if typed_command:
return typed_command
selected = (
payload.get("selected_todo")
if isinstance(payload.get("selected_todo"), dict)
else {}
)
todo_id = normalize_todo_id(selected.get("todo_id"))
todo_arg = f" --todo-id {todo_id}" if todo_id else ""
return (
f"loopx quota spend-slot --goal-id {goal_id} --slots 1 "
f"--source heartbeat --execute{todo_arg}{scoped_cli_args}"
)


def _terminal_cli_actions(
*,
mode: str,
goal_id: str,
scoped_cli_args: str,
payload: dict[str, Any],
settlement_plan: Mapping[str, Any] | None,
quota_spend_action: str,
capability_resolution_actions: list[str],
capability_reentry_actions: list[str],
) -> list[str]:
Expand Down Expand Up @@ -676,12 +653,7 @@ def _terminal_cli_actions(
*resolution_actions,
typed_writeback
or f"loopx refresh-state --goal-id {goal_id} --classification <validated_progress>{scoped_cli_args}",
_quota_spend_action(
goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
),
quota_spend_action,
]
if mode in {"user_gate", "user_todo_blocker_push", "user_action_required"}:
return [
Expand Down Expand Up @@ -757,6 +729,13 @@ def interaction_next_cli_actions(
)
is SchedulerRuntimeProfile.CODEX_APP_HEARTBEAT
)
quota_spend_action = build_quota_spend_action(
goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
scheduler_execution_context=scheduler_execution_context,
)
capability_resolution_actions = build_capability_resolution_writeback_actions(
payload.get("capability_gate"),
goal_id=goal_id,
Expand Down Expand Up @@ -846,12 +825,7 @@ def interaction_next_cli_actions(
f"loopx todo complete --goal-id {goal_id} --todo-id {monitor_todo_id}{lifecycle_actor_args} --evidence '<validated gate evidence>'",
f"loopx todo update --goal-id {goal_id} --todo-id {gated_todo_id}{lifecycle_actor_args} --note '<public-safe gate repair reason>'",
f"loopx refresh-state --goal-id {goal_id} --classification standing_monitor_gate_repair_recorded --delivery-batch-scale single_surface --delivery-outcome outcome_progress{settlement_args}{scoped_cli_args}",
_quota_spend_action(
goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
),
quota_spend_action,
]
route_candidates = (
agent_scope_frontier.get("route_continuation_replan_candidates")
Expand All @@ -862,12 +836,7 @@ def interaction_next_cli_actions(
return [
f"loopx todo add --goal-id {goal_id} --role agent --text '<public-safe route continuation advancement todo>'",
f"loopx refresh-state --goal-id {goal_id} --classification route_continuation_replan_recorded --delivery-batch-scale single_surface --delivery-outcome outcome_progress{settlement_args}{scoped_cli_args}",
_quota_spend_action(
goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
),
quota_spend_action,
]
candidates = (
agent_scope_frontier.get("deferred_resume_candidates")
Expand All @@ -883,12 +852,7 @@ def interaction_next_cli_actions(
"--note '<public-safe successor replan reason>'"
),
f"loopx refresh-state --goal-id {goal_id} --classification successor_replan_recorded --delivery-batch-scale single_surface --delivery-outcome outcome_progress{settlement_args}{scoped_cli_args}",
_quota_spend_action(
goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
),
quota_spend_action,
]
if (
mode == AgentScopeFrontierAction.AGENT_SCOPE_WAIT.value
Expand All @@ -904,12 +868,6 @@ def interaction_next_cli_actions(
typed_quota_guard,
]
if mode == "external_evidence_observation":
spend_action = _quota_spend_action(
goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
)
return [
"read approved controller/job/marker/writeback surfaces only",
(
Expand All @@ -921,7 +879,7 @@ def interaction_next_cli_actions(
),
(
"after that accountable writeback receipt only: "
f"{spend_action}; otherwise do not spend for unchanged observation"
f"{quota_spend_action}; otherwise do not spend for unchanged observation"
),
]
if mode == "agent_workspace_repair":
Expand All @@ -941,12 +899,7 @@ def interaction_next_cli_actions(
goal_id=goal_id,
settlement_args=settlement_args,
scoped_cli_args=scoped_cli_args,
quota_spend_action=_quota_spend_action(
goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
),
quota_spend_action=quota_spend_action,
replan_settlement_bound=bool(
settlement_identity.get("replan_obligation_id")
),
Expand All @@ -956,8 +909,8 @@ def interaction_next_cli_actions(
mode=mode,
goal_id=goal_id,
scoped_cli_args=scoped_cli_args,
payload=payload,
settlement_plan=settlement_plan,
quota_spend_action=quota_spend_action,
capability_resolution_actions=capability_resolution_actions,
capability_reentry_actions=capability_reentry_actions,
)
Expand Down
1 change: 1 addition & 0 deletions skills/loopx-self-repair/references/repair-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ teaches a reusable control-plane lesson.
| `normal_no_user_todo_misflagged` | `action_required=false` and user open count is zero, but the agent reports a state projection fault. | quota payload and user todo summary. | Rule over-applied the "todo not projected" fallback. | Allow "无用户待办/无需通知"; update heartbeat/prompt smoke if needed. |
| `todo_no_followup_global_terminal_overreach` | A completed scoped Todo records `done + no_followup`, but `refresh-state --repair-delta-kind no_followup` becomes `replan_noop` because unrelated advancement or long-running monitor Todos remain open. | rejected repair-delta claim, complete Agent Todo projection, scoped Todo metadata, and the latest local no-follow-up completion. | The repair-delta validator reused Goal-level terminal-frontier completeness to validate a Todo-local closure. | Keep Goal `terminal_no_followup` strict, but validate repair-delta no-follow-up against the latest scoped completed Todo in a complete parser projection; reject other-agent, continuation-required, malformed, or truncated evidence and keep the emitted Todo evidence bounded. Cover the real CLI path with an open monitor regression. |
| `terminal_settlement_ordering_gap` | Final Todo completion succeeds, then the matching refresh or spend is rejected because the Goal is already terminal; different hosts may expose different closeout orders or retry the material effect. | Typed settlement plan and effect identity, Todo completion event, accountable refresh and spend receipts, Turn journal phase prefix, and host task-body projection. | Final `no_followup` was modeled like ordinary pre-settlement Todo completion even though it mutates Goal terminal state and activates the strict no-more-spend guard. | Keep Goal terminal guards strict. Treat ordinary successor completion as Todo lifecycle work, but make final `no_followup` a conditional post-spend terminal closeout under the same effect identity. Require matching writeback and spend receipts at the mutation boundary, prove the closeout event itself carries `no_followup`, and journal it so closeout-only retry cannot repeat writeback or spend. Cover CLI, visible Goal, Codex App, Turn driver, adapter order, failure short-circuit, and replay. |
| `host_settlement_source_projection_gap` | A visible Goal host packet requires `source=visible-goal` without an explicit Turn receipt, but the nested interaction contract tells the agent to spend as `heartbeat` and binds the currently selected Todo. The command can fail after Todo completion or misattribute interactive usage. | Host runtime profile, canonical host quota commands, interaction-contract CLI actions, selected Todo lifecycle order, and the failed spend preview. | The generic interaction projection hard-coded heartbeat accounting and selected-Todo binding instead of deriving the accounting route from the scheduler execution context. | Route spend actions from the typed runtime profile. Native Goal runtimes preserve `source=visible-goal` and host-level attribution; heartbeat automation keeps its Todo- and Turn-bound settlement plan. Cover the exact CLI actions for each native Goal profile and retain the heartbeat regression. |
| `runtime_capability_user_gate_overreach` | A missing launcher/runtime capability such as `network` becomes `capability_gate.action=ask_owner`, `requires_user_action=true`, or a user notification even though no credential, protected access, or production authority is needed. | quota `capability_gate`, heartbeat `--available-capability` inputs, launcher/runtime observations, interaction contract user channel. | Capability availability and permission authority were collapsed into one owner-held class. | Keep runtime provisioning gaps in the agent repair lane, require truthful runtime observation before declaring availability, and reserve `ask_owner` for credentials or protected/production authority. Cover all-blocked and mixed-capability cases with capability-gate and monitor smokes. |
| `visible_goal_dynamic_capability_projection_gap` | A visible Goal session proves a runtime capability such as `network` at a real callsite, but later continuations keep running the activation-time quota command without that capability and repeatedly enter `capability_bridge_repair`; a bounded host tool result truncates before the re-entry packet; or a same-turn re-entry selects a newly runnable Todo and conflicts with the heartbeat receipt already bound to the admitted Todo. | Activation-time capability list, later real-callsite observation, complete versus host-captured quota byte counts, byte offset of `runtime_capability_reentry_v0`, quota guard and follow-up argv, capability-gate result, heartbeat receipt settlement identity, and re-entry selected Todo. | The CLI decision surface lacked a typed re-entry packet, emitted it after diagnostics larger than the host capture budget, or recomputed Todo priority without treating the committed same-turn settlement identity as an immutable selection binding. | Keep the Goal prompt stable. Return the real-callsite-gated re-entry packet near the beginning of JSON CLI output; after verified re-entry, preserve observed capabilities in generated follow-up actions and prefer the receipt-bound Todo when it remains runnable. Fail closed on an unavailable bound Todo or an explicit conflicting Todo request. Never persist observations as grants. Cover blocked and bounded-capture packets, successful re-entry inheritance, owner-capability exclusion, receipt-bound replay, and explicit conflict. |
| `blocked_priority_fallback_hidden` | A core P0 path is user-gated, safe P1/P2 fallback runs, but the user is not told what blocked the P0 path. | quota `safe_bypass_allowed`, active priority stack, final heartbeat text. | Fallback execution hid the higher-priority blocked item. | Report concrete blocker plus fallback progress; expose `blocked_priority_fallback` in state/payload if missing. |
Expand Down
34 changes: 34 additions & 0 deletions tests/control_plane/test_quota_settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,40 @@ def test_standard_codex_app_actions_use_typed_settlement_before_turn_driver() ->
assert '--turn-instance-id "${LOOPX_TURN:?}"' in command


@pytest.mark.parametrize(
"profile",
(
SchedulerRuntimeProfile.ARK_MANAGED_AGENT_GOAL,
SchedulerRuntimeProfile.CODEX_APP_SSH_VISIBLE,
SchedulerRuntimeProfile.CODEX_CLI_VISIBLE,
),
)
def test_native_goal_actions_preserve_visible_goal_spend_attribution(
profile: SchedulerRuntimeProfile,
) -> None:
todo_id = "todo_visible_goal"
actions = interaction_next_cli_actions(
{
"goal_id": GOAL_ID,
"agent_identity": {"agent_id": AGENT_ID},
"selected_todo": {"todo_id": todo_id},
},
mode="bounded_delivery",
scheduler_execution_context=scheduler_execution_context_for_runtime_profile(
profile
),
)

assert len(actions) == 2
assert actions[0].startswith("loopx refresh-state")
assert actions[1] == (
f"loopx quota spend-slot --goal-id {GOAL_ID} --slots 1 "
f"--source visible-goal --execute --agent-id {AGENT_ID}"
)
assert all("--todo-id" not in command for command in actions)
assert all("--turn-instance-id" not in command for command in actions)


def test_codex_app_external_observation_settles_only_substantive_writeback() -> None:
todo_id = "todo_external_observation"
actions = interaction_next_cli_actions(
Expand Down