Skip to content

Commit 136fa88

Browse files
committed
fix(quota): preserve deferred receipt-bound turn identity
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
1 parent 4a41193 commit 136fa88

4 files changed

Lines changed: 165 additions & 0 deletions

File tree

‎loopx/control_plane/quota/should_run_packet.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
)
8686
from ..todos.contract import (
8787
normalize_todo_claimed_by,
88+
normalize_todo_id,
8889
)
8990
from ..todos.todo_semantics import (
9091
todo_item_is_actionable_open as projection_todo_item_is_actionable_open,
@@ -123,6 +124,7 @@
123124
user_action_owns_empty_agent_lane_from_summaries as _user_action_owns_empty_agent_lane,
124125
)
125126
from ..work_items.work_lane import (
127+
WORK_LANE_RECEIPT_BOUND_DEFERRED_OBLIGATION,
126128
work_lane_contract_is_due_monitor_attempt,
127129
work_lane_contract_is_receipt_bound_monitor_settled,
128130
)
@@ -533,6 +535,15 @@ def _resolve_agent_lane_delivery_route(
533535
# decision. A newly runnable Todo remains visible in summaries, but it
534536
# cannot become the selected settlement target in the same packet.
535537
fallback = None
538+
if (
539+
prepared.receipt_bound_todo_id
540+
and isinstance(fallback, dict)
541+
and normalize_todo_id(fallback.get("todo_id"))
542+
!= prepared.receipt_bound_todo_id
543+
):
544+
# Feed only the committed identity into the TS delivery router. The
545+
# independent successor remains discoverable on a fresh Turn.
546+
fallback = None
536547

537548
delivery_agent_id = normalize_todo_claimed_by(
538549
(prepared.agent_identity or {}).get("agent_id")
@@ -617,6 +628,17 @@ def _resolve_agent_lane_delivery_route(
617628
else:
618629
selected_action = None
619630

631+
if (
632+
prepared.receipt_bound_todo_id
633+
and isinstance(selected_action, dict)
634+
and normalize_todo_id(selected_action.get("todo_id"))
635+
!= prepared.receipt_bound_todo_id
636+
):
637+
# The router may find an independent successor after the bound Todo
638+
# becomes unavailable. That successor cannot replace an already
639+
# committed settlement identity inside the same heartbeat Turn.
640+
return None
641+
620642
boundary = delivery_route.get("boundary")
621643
if (
622644
isinstance(selected_action, dict)
@@ -867,6 +889,31 @@ def _resolve_quota_should_run_route(
867889
"reason": reason,
868890
"spend_policy": "no quota spend for an already-settled heartbeat turn",
869891
}
892+
receipt_bound_deferred_wait = bool(
893+
prepared.receipt_bound_todo_id
894+
and isinstance(prepared.work_lane_contract, dict)
895+
and prepared.work_lane_contract.get("obligation")
896+
== WORK_LANE_RECEIPT_BOUND_DEFERRED_OBLIGATION
897+
)
898+
if receipt_bound_deferred_wait:
899+
normal_delivery_allowed = recovery_allowed = self_repair_allowed = False
900+
capability_repair_allowed = workspace_repair_allowed = False
901+
replan_decision_allowed = receipt_bound_replan_decision = False
902+
should_run = False
903+
effective_action = EffectiveAction.QUOTA_SKIP.value
904+
reason = (
905+
"the Todo bound to this heartbeat receipt is deferred; do not "
906+
"select or spend an independent successor in the same Turn"
907+
)
908+
quota = {**quota, "safe_bypass_allowed": False}
909+
heartbeat_recommendation = {
910+
**heartbeat_recommendation,
911+
"recommended_mode": effective_action,
912+
"notify": "DONT_NOTIFY",
913+
"reason": reason,
914+
"spend_policy": "no quota spend for a deferred receipt-bound Todo",
915+
"stop_if_unchanged": True,
916+
}
870917
monitor_quiet_skip = (
871918
not replan_decision_allowed
872919
and normal_delivery_allowed

‎loopx/control_plane/quota/should_run_prepare.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
)
7070
from ..todos.contract import (
7171
TODO_STATUS_BLOCKED,
72+
TODO_STATUS_DEFERRED,
7273
TODO_STATUS_OPEN,
7374
TODO_TASK_CLASS_ADVANCEMENT,
7475
TODO_TASK_CLASS_BLOCKER,
@@ -104,6 +105,7 @@
104105
lark_inbox_reply_due_work_lane_contract,
105106
operator_inbox_material_review_due_work_lane_contract,
106107
preserve_heartbeat_receipt_bound_work_lane,
108+
receipt_bound_deferred_work_lane,
107109
scoped_user_gate_due_monitor_contract,
108110
work_lane_contract_is_lark_inbox_reply_due,
109111
work_lane_contract_is_operator_inbox_material_review_due,
@@ -742,6 +744,19 @@ def _prepare_quota_should_run_item(
742744
)
743745
if isinstance(preserved_work_lane, dict):
744746
work_lane_contract = preserved_work_lane
747+
elif any(
748+
normalize_todo_id(source_item.get("todo_id")) == receipt_bound_todo_id
749+
and normalize_todo_status(source_item.get("status"))
750+
== TODO_STATUS_DEFERRED
751+
for source_item in agent_todo_planning_source_items
752+
):
753+
# The old Turn still owns its committed settlement identity, but a
754+
# deferred Todo is not an executable candidate. A successor may be
755+
# selected only by a fresh Turn; do not leak it through work-lane
756+
# fallback on this replay.
757+
work_lane_contract = receipt_bound_deferred_work_lane(
758+
todo_id=receipt_bound_todo_id,
759+
)
745760
if inbox_priority_due:
746761
task_orchestration_contract = capability_gate = capability_monitor_contract = None
747762
capability_monitor_fallback = scoped_user_gate_fallback = workspace_guard = None

‎loopx/control_plane/work_items/work_lane.py‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def observe_work_lane(
3939
WORK_LANE_RECEIPT_BOUND_MONITOR_SETTLED_OBLIGATION = (
4040
"finish_settled_receipt_bound_monitor_turn"
4141
)
42+
WORK_LANE_RECEIPT_BOUND_DEFERRED_OBLIGATION = (
43+
"wait_for_receipt_bound_deferred_todo"
44+
)
4245
WORK_LANE_CURRENT_AGENT_MONITOR_REPAIR_OBLIGATIONS = {
4346
"attempt_due_monitor",
4447
"repair_monitor_schedule_metadata",
@@ -278,6 +281,33 @@ def preserve_heartbeat_receipt_bound_work_lane(
278281
}
279282

280283

284+
def receipt_bound_deferred_work_lane(
285+
*, todo_id: str,
286+
) -> dict[str, Any]:
287+
"""Keep an immutable Turn binding visible without executing a deferred Todo."""
288+
289+
normalized = normalize_todo_id(todo_id)
290+
if not normalized:
291+
raise ValueError("receipt-bound deferred work lane requires a Todo id")
292+
return {
293+
"schema_version": WORK_LANE_CONTRACT_SCHEMA_VERSION,
294+
"lane": "advancement_task",
295+
"obligation": WORK_LANE_RECEIPT_BOUND_DEFERRED_OBLIGATION,
296+
"must_attempt_work": False,
297+
"selection_binding": "heartbeat_receipt",
298+
"selected_todo_id": normalized,
299+
"reason_codes": [
300+
"heartbeat_receipt_bound_replay",
301+
"receipt_bound_todo_deferred",
302+
"successor_requires_fresh_turn",
303+
],
304+
"action": (
305+
"the Todo bound to this heartbeat turn is deferred; do not execute "
306+
"or spend this turn, and select independent work under a fresh turn"
307+
),
308+
}
309+
310+
281311
def work_lane_contract_is_lark_inbox_reply_due(
282312
contract: dict[str, Any] | None,
283313
) -> bool:

‎tests/control_plane/test_quota_settlement_cli.py‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3424,6 +3424,79 @@ def test_pending_deferred_p0_allows_independent_p1_selection(
34243424
assert payload["action_selection_qualification"]["state"] == "qualified"
34253425

34263426

3427+
def test_same_turn_bound_p0_does_not_project_p1_after_p0_becomes_deferred(
3428+
tmp_path: Path,
3429+
) -> None:
3430+
project, runtime, registry_path = _write_fixture(tmp_path)
3431+
_configure_selectable_alternative(project)
3432+
state_path = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md"
3433+
state_path.write_text(
3434+
state_path.read_text(encoding="utf-8").replace(
3435+
"[P1] Validate and settle the selected delivery.",
3436+
"[P0] Validate and settle the selected delivery.",
3437+
),
3438+
encoding="utf-8",
3439+
)
3440+
turn_id = "turn-bound-p0-then-deferred"
3441+
guard = (
3442+
"quota", "should-run", "--codex-app", "--goal-id", GOAL_ID,
3443+
"--agent-id", AGENT_ID, "--turn-instance-id", turn_id,
3444+
"--scan-path", str(project),
3445+
)
3446+
first_rc, first = _run_cli(registry_path, runtime, *guard, "--todo-id", TODO_ID)
3447+
assert first_rc == 0, first
3448+
assert first["heartbeat_receipt"]["settlement_identity"]["todo_id"] == TODO_ID
3449+
state_path.write_text(
3450+
state_path.read_text(encoding="utf-8").replace(
3451+
f"todo_id={TODO_ID} status=open",
3452+
f"todo_id={TODO_ID} status=deferred "
3453+
"resume_when=resume_at:2099-01-01T00:00:00Z",
3454+
),
3455+
encoding="utf-8",
3456+
)
3457+
replay_rc, replay = _run_cli(registry_path, runtime, *guard)
3458+
assert replay_rc == 0, replay
3459+
assert replay["effective_action"] == "quota_skip"
3460+
assert replay["should_run"] is False
3461+
assert replay["heartbeat_receipt"]["status"] == "replayed"
3462+
assert replay["heartbeat_receipt"]["settlement_identity"]["todo_id"] == TODO_ID
3463+
assert replay["selected_todo"]["todo_id"] == TODO_ID
3464+
assert replay["work_lane_contract"]["obligation"] == (
3465+
"wait_for_receipt_bound_deferred_todo"
3466+
)
3467+
assert replay["work_lane_contract"]["must_attempt_work"] is False
3468+
interaction = replay["interaction_contract"]
3469+
assert interaction["agent_channel"]["must_attempt"] is False
3470+
assert interaction["cli_channel"]["spend_after_validation"] is False
3471+
assert ALTERNATIVE_TODO_ID not in json.dumps(
3472+
interaction["cli_channel"].get("next_cli_actions", [])
3473+
)
3474+
plan = interaction["cli_channel"].get("settlement_plan")
3475+
assert plan is None or plan["identity"]["todo_id"] == TODO_ID
3476+
assert _heartbeat_receipt_count(runtime, turn_id) == 1
3477+
assert _spend_run_count(runtime) == 0
3478+
3479+
conflict_rc, conflict = _run_cli(
3480+
registry_path, runtime, *guard, "--todo-id", ALTERNATIVE_TODO_ID,
3481+
)
3482+
assert conflict_rc != 0, conflict
3483+
assert conflict["error_code"] in {
3484+
"heartbeat_receipt_identity_conflict",
3485+
"quota_action_selection_rejected",
3486+
}
3487+
assert _heartbeat_receipt_count(runtime, turn_id) == 1
3488+
3489+
next_rc, next_turn = _run_cli(
3490+
registry_path, runtime,
3491+
"quota", "should-run", "--codex-app",
3492+
"--goal-id", GOAL_ID, "--agent-id", AGENT_ID,
3493+
"--turn-instance-id", "turn-after-bound-p0-deferred",
3494+
"--scan-path", str(project), "--todo-id", ALTERNATIVE_TODO_ID,
3495+
)
3496+
assert next_rc == 0, next_turn
3497+
assert next_turn["selected_todo"]["todo_id"] == ALTERNATIVE_TODO_ID
3498+
3499+
34273500
def test_pending_selection_preserves_workspace_repair_then_reenters_same_turn(
34283501
tmp_path: Path,
34293502
) -> None:

0 commit comments

Comments
 (0)