From 668bed2f03a274f273d39c187121a1a4ede67165 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sat, 7 Nov 2026 12:31:52 +0800 Subject: [PATCH] perf(quota): share settlement readback with reward memory Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../project_lifecycle_refresh_state.py | 8 ++---- loopx/cli_commands/quota.py | 10 +++----- loopx/cli_commands/quota_reward_memory.py | 25 ++++--------------- loopx/control_plane/quota/settlement_cli.py | 9 ++++--- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/loopx/cli_commands/project_lifecycle_refresh_state.py b/loopx/cli_commands/project_lifecycle_refresh_state.py index e34037aa89..fc9989189f 100644 --- a/loopx/cli_commands/project_lifecycle_refresh_state.py +++ b/loopx/cli_commands/project_lifecycle_refresh_state.py @@ -844,14 +844,10 @@ def handle_refresh_state_command( if (material_refresh_ready and isinstance(candidate, Mapping) and candidate.get("validation_bound") is True): # A late reflection may recover after spend already committed. - # Read the same settlement; never repeat its accounting effect. + # Reuse the settlement read after recording the refresh event. attach_reward_memory_ingest_after_spend( payload, execute=True, registry_path=registry_path, - runtime_root=runtime_root, goal_id=args.goal_id, - agent_id=args.agent_id, todo_id=args.todo_id, - turn_instance_id=args.turn_instance_id, - replan_obligation_id=getattr(args, "replan_obligation_id", None), - goal_ref=goal_ref, + runtime_root=runtime_root, readback=settlement_readback, ) if material_refresh_ready and post_writeback_hooks: settlement_identity = ( diff --git a/loopx/cli_commands/quota.py b/loopx/cli_commands/quota.py index 0cb147ab61..92339b96f0 100644 --- a/loopx/cli_commands/quota.py +++ b/loopx/cli_commands/quota.py @@ -506,9 +506,10 @@ def _record_non_guard_quota_event( ) if args.quota_command == "spend-slot" and spend_turn_instance_id: assert context is not None # A settlement requires a prepared authority context. - _export("attach_spend_settlement_result")( + readback = _export("attach_spend_settlement_result")( payload, runtime_root=context.runtime_root, + registry_path=registry_path, goal_id=args.goal_id, agent_id=args.agent_id, todo_id=rollout_todo_id, @@ -521,12 +522,7 @@ def _record_non_guard_quota_event( execute=bool(args.execute), runtime_root=context.runtime_root, registry_path=registry_path, - goal_id=args.goal_id, - agent_id=args.agent_id, - todo_id=rollout_todo_id, - turn_instance_id=spend_turn_instance_id, - replan_obligation_id=rollout_replan_obligation_id, - goal_ref=goal_ref, + readback=readback, ) diff --git a/loopx/cli_commands/quota_reward_memory.py b/loopx/cli_commands/quota_reward_memory.py index b4d2147c62..17580d090b 100644 --- a/loopx/cli_commands/quota_reward_memory.py +++ b/loopx/cli_commands/quota_reward_memory.py @@ -4,7 +4,10 @@ from collections.abc import Mapping from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from ..control_plane.quota.settlement import QuotaSettlementReadback def stage_reward_memory_outcome_candidate( @@ -65,30 +68,12 @@ def attach_reward_memory_ingest_after_spend( execute: bool, runtime_root: Path, registry_path: Path, - goal_id: str, - agent_id: str, - todo_id: str | None, - turn_instance_id: str, - replan_obligation_id: str | None, - goal_ref: Mapping[str, Any] | None = None, + readback: QuotaSettlementReadback, ) -> None: if not execute or payload.get("ok") is not True: return from ..capabilities.reward_memory.turn_hooks import run_staged_memory_settlement - from ..control_plane.quota.settlement import read_heartbeat_settlement - readback = read_heartbeat_settlement( - runtime_root, - goal_id=goal_id, - agent_id=agent_id, - todo_id=todo_id, - turn_instance_id=turn_instance_id, - replan_obligation_id=replan_obligation_id, - registry_path=registry_path, - goal_ref=goal_ref, - ) - if readback is None: - return identity = readback.identity.value writeback_event = readback.writeback_event details_value = ( diff --git a/loopx/control_plane/quota/settlement_cli.py b/loopx/control_plane/quota/settlement_cli.py index bf3e2cd458..c57e084a1f 100644 --- a/loopx/control_plane/quota/settlement_cli.py +++ b/loopx/control_plane/quota/settlement_cli.py @@ -22,6 +22,7 @@ requalify_bound_heartbeat_receipt, ) from .settlement import ( + QuotaSettlementReadback, attach_settlement_progress, read_heartbeat_settlement, settlement_result_payload, @@ -460,16 +461,15 @@ def attach_spend_settlement_result( payload: dict[str, object], *, runtime_root: Path, + registry_path: Path, goal_id: str, agent_id: str | None, todo_id: str | None, turn_instance_id: str, replan_obligation_id: str | None = None, goal_ref: Mapping[str, object] | None = None, -) -> None: - registry_path = ( - Path(str(payload["registry"])) if payload.get("registry") else None - ) +) -> QuotaSettlementReadback: + """Attach receipts and return the same post-event observation to the caller.""" readback = read_heartbeat_settlement( runtime_root, goal_id=goal_id, @@ -500,3 +500,4 @@ def attach_spend_settlement_result( elif payload.get("receipt_repair_required"): payload["receipt_repair_required"] = False payload["receipt_repaired"] = True + return readback