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
11 changes: 11 additions & 0 deletions docs/quota-allocation.md
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,17 @@ leases this resumes unchanged work without granting execution authority; the
worker must then acquire a fresh lease. Bundling `--note`, evidence or other
execution edits into that command remains subject to the normal lease fence.

A receipt initially admitted for `successor_replan_required` retains that
bounded lifecycle duty on replay while the same canonical, claim-scoped resume
condition remains ready. It must not become a quiet skip merely because the
worker has not reopened it yet. Reopening permits ordinary same-Todo
qualification under the original Turn identity; it neither substitutes another
Todo nor imports a newly discovered semantic replan obligation into an explicit
empty original guard. A later future wait, changed owner or completed
settlement still closes this resume path. This differs from executable work
which became deferred after admission: that existing no-execution/no-spend
boundary remains intact, and independent work is selected under a fresh Turn.

Priority remains authoritative across the resume boundary. When a ready
current-agent or unclaimed deferred successor has strictly higher priority than
the selected open advancement todo, quota also returns
Expand Down
3 changes: 2 additions & 1 deletion loopx/control_plane/quota/live_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ def _receipt_bound_guard_fields(
receipt = getattr(readback, "heartbeat_receipt", None) or {}
return {
"receipt_bound_replan_guard_scoped": bool(
obligation_id and isinstance(guard, Mapping)
isinstance(guard, Mapping)
and guard.get("scope") == "turn_guard"
and guard.get("selected_obligation_id") == obligation_id
),
"receipt_bound_guard_recorded_at": receipt.get("recorded_at"),
"receipt_bound_successor_replan": bool(getattr(readback, "successor_replan_pending", False)),
}


Expand Down
2 changes: 2 additions & 0 deletions loopx/control_plane/quota/settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class QuotaSettlementReadback:
progress: dict[str, Any] | None = None
native_child_admission: dict[str, Any] | None = None
heartbeat_reentry_qualification: dict[str, Any] | None = None
successor_replan_pending: bool = False


def attach_settlement_progress(
Expand Down Expand Up @@ -439,6 +440,7 @@ def read(admission: Mapping[str, Any] | None) -> Any:
progress=_optional_readback_record(payload.get("progress")),
native_child_admission=_optional_readback_record(payload.get("native_child_admission")),
heartbeat_reentry_qualification=_optional_readback_record(payload.get("heartbeat_reentry_qualification")),
successor_replan_pending=payload.get("successor_replan_pending") is True,
spend_run=_optional_readback_record(payload.get("spend_run")),
heartbeat_receipt=_optional_readback_record(payload.get("heartbeat_receipt")),
writeback_event=_optional_readback_record(payload.get("writeback_event")),
Expand Down
3 changes: 3 additions & 0 deletions loopx/control_plane/quota/settlement_readback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,9 @@ function readQuotaSettlementFromRequest(
completion_event: completionEvent,
monitor_phase: monitorPhase,
replay_phase: replayPhase,
successor_replan_pending: replayPhase === "open" &&
heartbeatReceipt.status === "successor_replan_required" &&
receiptDetails.should_run === true && receiptDetails.delivery_allowed === false,
native_child_admission: nativeChildReportAdmission(
receiptDetails, heartbeatReceipt.status, identity.effect_id, replayPhase,
closeoutStarted,
Expand Down
2 changes: 2 additions & 0 deletions loopx/control_plane/quota/should_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,7 @@ def build_quota_should_run(
receipt_bound_terminal_phase: ReceiptBoundTerminalPhase | None = None,
receipt_bound_replan_obligation_id: str | None = None,
receipt_bound_replan_guard_scoped: bool = False,
receipt_bound_successor_replan: bool = False,
receipt_bound_guard_recorded_at: str | None = None,
turn_instance_id: str | None = None,
runtime_root: str | Path | None = None,
Expand Down Expand Up @@ -587,6 +588,7 @@ def build_quota_should_run(
receipt_bound_replay_phase=receipt_bound_replay_phase,
receipt_bound_replan_obligation_id=receipt_bound_replan_obligation_id,
receipt_bound_replan_guard_scoped=receipt_bound_replan_guard_scoped,
receipt_bound_successor_replan=receipt_bound_successor_replan,
authoritative_fallback_todo_items=authoritative_fallback_todo_items,
replan_explore_reader=tracked_replan_explore_reader,
receipt_bound_guard_recorded_at=receipt_bound_guard_recorded_at,
Expand Down
26 changes: 16 additions & 10 deletions loopx/control_plane/quota/should_run_packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,10 +700,10 @@ def _resolve_agent_lane_delivery_route(
return selected_action


def _scoped_gate_deferred_replan_frontier(
def _selected_deferred_replan_frontier(
prepared: _QuotaDecisionPreparation,
) -> dict[str, Any] | None:
"""Keep an authoritative gate fallback in the replan lane.
"""Keep an authoritative selected resume in the replan lane.

The fallback selector may choose a ready deferred successor while an open
advancement remains covered by the gate. That choice is a lifecycle replan,
Expand All @@ -716,13 +716,19 @@ def _scoped_gate_deferred_replan_frontier(
if isinstance(fallback, dict)
else None
)
receipt_resume = prepared.receipt_bound_successor_replan
if receipt_resume:
selected = next((candidate for candidate in _agent_scope_deferred_resume_candidates(
prepared.agent_todo_summary or {},
agent_id=normalize_todo_claimed_by((prepared.agent_identity or {}).get("agent_id")),
) if normalize_todo_id(candidate.get("todo_id")) == prepared.receipt_bound_todo_id), None)
agent_id = normalize_todo_claimed_by(
(prepared.agent_identity or {}).get("agent_id")
)
if (
not agent_id
or not isinstance(selected, dict)
or selected.get("fallback_kind") != "deferred_successor_replan"
or (not receipt_resume and selected.get("fallback_kind") != "deferred_successor_replan")
):
return None
todo_id = normalize_todo_id(selected.get("todo_id")) or "<todo_id>"
Expand All @@ -732,7 +738,7 @@ def _scoped_gate_deferred_replan_frontier(
quiet_noop_allowed=False,
spend_policy="spend once after validated successor replan/todo writeback",
reason=(
f"scoped user gate fallback selected ready deferred successor {todo_id}; "
f"selected ready deferred successor {todo_id}; "
"resolve its lifecycle before selecting another advancement"
),
recommended_action=(
Expand All @@ -743,7 +749,7 @@ def _scoped_gate_deferred_replan_frontier(
candidate_counts={"deferred_resume_candidate_count": 1},
extra_fields={
"deferred_resume_candidates": [selected],
"scoped_user_gate_fallback": True,
"scoped_user_gate_fallback": not receipt_resume,
},
)

Expand Down Expand Up @@ -1135,18 +1141,18 @@ def _resolve_quota_should_run_route(
elif (
not replan_decision_allowed
and not receipt_bound_monitor_settled
and prepared.receipt_bound_todo_id is None
and (prepared.receipt_bound_todo_id is None or prepared.receipt_bound_successor_replan)
):
# The typed receipt owner already selected this Turn's immutable
# binding. Frontier discovery is for unbound Turns: a newly ready
# successor must not replace an interrupted Turn's Todo/effect after
# the receipt-bound delivery route has been resolved above.
# binding. Only its original, still-ready resume replan can reenter
# this frontier. An unrelated newly ready successor cannot replace
# an interrupted Turn's Todo/effect.
selected_recommended_action = selected_action_with_agent_lane(
selected_recommended_action,
agent_lane_next_action=agent_lane_next_action,
)
agent_scope_frontier = (
_scoped_gate_deferred_replan_frontier(prepared)
_selected_deferred_replan_frontier(prepared)
or _agent_scope_no_candidate_frontier(
agent_identity=prepared.agent_identity,
agent_todo_summary=prepared.agent_todo_summary,
Expand Down
64 changes: 43 additions & 21 deletions loopx/control_plane/quota/should_run_prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
scope_status_item_to_agent_lane as _scope_status_item_to_agent_lane,
)
from ..agents.agent_scope import (
_agent_scope_deferred_resume_candidates,
_agent_scoped_user_todo_override,
_scoped_user_gate_fallback,
)
Expand Down Expand Up @@ -139,6 +140,7 @@ class _QuotaDecisionPreparation:
effective_available_capabilities: Any
runtime_available_capabilities: Any
receipt_bound_todo_id: str | None
receipt_bound_successor_replan: bool
requested_action_todo_id: str | None
requested_action_candidate: dict[str, Any] | None
action_selection_qualification: dict[str, Any] | None
Expand Down Expand Up @@ -198,7 +200,7 @@ def _preserve_receipt_bound_replan_obligation(
preserved_replan_id = normalize_todo_replan_obligation_id(
receipt_bound_replan_obligation_id
)
if not preserved_replan_id:
if not preserved_replan_id and not guard_scoped:
return dict(replan_obligation) if replan_obligation is not None else None
try:
result = project_replan_semantics({
Expand All @@ -215,6 +217,8 @@ def _preserve_receipt_bound_replan_obligation(
raise HeartbeatReceiptIdentityConflictError(str(exc)) from None
raise
obligation = result.get("obligation") if isinstance(result, Mapping) else None
if obligation is None and preserved_replan_id is None and guard_scoped:
return None
if not isinstance(obligation, Mapping) or obligation.get("obligation_id") != preserved_replan_id:
raise RuntimeError("TypeScript receipt-bound replan obligation shape mismatch")
return dict(obligation)
Expand Down Expand Up @@ -503,6 +507,35 @@ def _with_auxiliary_gate_scope(
return {**work_lane, "auxiliary_monitor_poll": {**auxiliary, "gate_scope": selected_scope}}


def _pending_receipt_resume_is_ready(
pending: bool, goal_health_ok: bool, state: str,
summary: dict[str, Any] | None, identity: dict[str, Any] | None,
todo_id: str | None,
) -> bool:
# The receipt is a resume duty, not authority to release a new/future wait.
return pending and goal_health_ok and state == "eligible" and any(
normalize_todo_id(candidate.get("todo_id")) == todo_id
for candidate in _agent_scope_deferred_resume_candidates(
summary,
agent_id=normalize_todo_claimed_by((identity or {}).get("agent_id")),
)
)


def _receipt_delivery_continuity(
payload: dict[str, Any], goal_id: str, agent_id: str | None,
todos: list[dict[str, Any]],
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
anchor = (
_latest_accountable_agent_delivery(payload, goal_id=goal_id, agent_id=agent_id)
if agent_id else None
)
todo_id = normalize_todo_id((anchor or {}).get("todo_id"))
todo = next((candidate for candidate in todos
if normalize_todo_id(candidate.get("todo_id")) == todo_id), None)
return anchor, todo


def _prepare_quota_should_run_item(
status_payload: dict[str, Any],
*,
Expand All @@ -525,6 +558,7 @@ def _prepare_quota_should_run_item(
receipt_bound_replay_phase: ReceiptBoundReplayPhase | None,
receipt_bound_replan_obligation_id: str | None,
receipt_bound_replan_guard_scoped: bool = False,
receipt_bound_successor_replan: bool = False,
authoritative_fallback_todo_items: FallbackTodoSource = None,
replan_explore_reader: Callable[[str, str | None], Mapping[str, Any] | None] | None = None,
receipt_bound_guard_recorded_at: str | None = None,
Expand Down Expand Up @@ -618,25 +652,8 @@ def _prepare_quota_should_run_item(
recovery_allowed = False
reason = str(quota["reason"])
boundary_agent_id = normalize_todo_claimed_by((agent_identity or {}).get("agent_id"))
delivery_continuity_anchor = (
_latest_accountable_agent_delivery(
status_payload,
goal_id=safe_goal_id,
agent_id=boundary_agent_id,
)
if boundary_agent_id
else None
)
continuity_todo_id = normalize_todo_id(
(delivery_continuity_anchor or {}).get("todo_id")
)
delivery_continuity_todo = next(
(
candidate
for candidate in agent_todo_source_items
if normalize_todo_id(candidate.get("todo_id")) == continuity_todo_id
),
None,
delivery_continuity_anchor, delivery_continuity_todo = _receipt_delivery_continuity(
status_payload, safe_goal_id, boundary_agent_id, agent_todo_source_items,
)
reward_memory_experiment_status = _resolve_reward_memory_experiment_from_status(
status_payload,
Expand Down Expand Up @@ -747,6 +764,10 @@ def _prepare_quota_should_run_item(
)
inbox_priority_due = inbox_reply_due or inbox_material_review_due
receipt_bound_agent_next_action = None
receipt_bound_successor_replan = _pending_receipt_resume_is_ready(
receipt_bound_successor_replan, goal_health_ok, state,
agent_todo_summary, agent_identity, receipt_bound_todo_id,
)
if (
receipt_bound_todo_id
and not inbox_priority_due
Expand Down Expand Up @@ -800,7 +821,7 @@ def _prepare_quota_should_run_item(
)
or work_lane_contract
)
else:
elif not receipt_bound_successor_replan:
# The old Turn still owns its committed settlement identity, but a
# deferred Todo is not an executable candidate. A successor may be
# selected only by a fresh Turn; do not leak it through work-lane
Expand Down Expand Up @@ -920,6 +941,7 @@ def _prepare_quota_should_run_item(
effective_available_capabilities=effective_available_capabilities,
runtime_available_capabilities=availability["runtime_available"],
receipt_bound_todo_id=receipt_bound_todo_id,
receipt_bound_successor_replan=receipt_bound_successor_replan,
requested_action_todo_id=requested_action_todo_id,
requested_action_candidate=requested_action_candidate,
action_selection_qualification=None,
Expand Down
2 changes: 1 addition & 1 deletion loopx/control_plane/work_items/replan_semantics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ function preserveReceiptBoundObligation(request: JsonObject): JsonObject {
const selected = optionalNonEmptyString(request.selected_obligation_id, "selected_obligation_id");
const current = request.current_obligation == null ? null
: requireJsonObject(request.current_obligation, "current_obligation");
if (selected === null) return {obligation: current};
if (selected === null) return {obligation: request.guard_scoped === true ? null : current};
if (!/^replan-[a-f0-9]{16}$/.test(selected) || typeof request.guard_scoped !== "boolean" ||
!Array.isArray(request.transition_candidates) ||
![null, "open", "settlement_pending"].includes(request.replay_phase as string | null)) {
Expand Down
2 changes: 2 additions & 0 deletions loopx/quota.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,7 @@ def build_quota_should_run(
receipt_bound_terminal_phase: ReceiptBoundTerminalPhase | None = None,
receipt_bound_replan_obligation_id: str | None = None,
receipt_bound_replan_guard_scoped: bool = False,
receipt_bound_successor_replan: bool = False,
receipt_bound_guard_recorded_at: str | None = None,
turn_instance_id: str | None = None,
runtime_root: str | Path | None = None,
Expand Down Expand Up @@ -962,6 +963,7 @@ def read_explore_context(read_goal_id: str, read_agent_id: str | None):
receipt_bound_terminal_phase=receipt_bound_terminal_phase,
receipt_bound_replan_obligation_id=receipt_bound_replan_obligation_id,
receipt_bound_replan_guard_scoped=receipt_bound_replan_guard_scoped,
receipt_bound_successor_replan=receipt_bound_successor_replan,
receipt_bound_guard_recorded_at=receipt_bound_guard_recorded_at,
turn_instance_id=turn_instance_id,
runtime_root=runtime_root,
Expand Down
2 changes: 1 addition & 1 deletion loopx/semantics/vocabulary_v0.json
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@
"loopx/control_plane/agents/agent_scope.py::_other_agent_or_exhausted_frontier",
"loopx/control_plane/agents/agent_scope.py::_route_continuation_frontier",
"loopx/control_plane/agents/agent_scope.py::_selected_candidate_priority_frontier",
"loopx/control_plane/quota/should_run_packet.py::_scoped_gate_deferred_replan_frontier"
"loopx/control_plane/quota/should_run_packet.py::_selected_deferred_replan_frontier"
],
"call_producers": {
"loopx/control_plane/agents/agent_scope_frontier.py::build_agent_scope_frontier_payload": [
Expand Down
11 changes: 9 additions & 2 deletions skills/loopx-change-quality/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,5 +208,12 @@ Report:
- receipt id and exact verification status;
- premerge status, failures or skips, and manual holds.

Stop before delivery when strict policy requires a receipt and the receipt is
missing, invalid, stale, or contains an unresolved blocker.
Hold merge, promotion and claims of qualified delivery when strict policy
requires a receipt and it is missing, invalid, stale, or contains an unresolved
blocker. Continue feasible authorized repair and rerun qualification; a quality
hold does not itself make an Agent task an external wait. Preserve the actual
required checks and failed observations. When a real dependency prevents
repair, record its typed wait and consider other authorized Goal work through
the current selection contract. A completed author proposal may enter normal
PR review with its qualification hold disclosed; this grants no merge or
installation authority.
Loading
Loading