Skip to content

Commit aedb376

Browse files
authored
Merge pull request #6245 from loopx-project/codex/receipt-next-action-isolation
fix(turn): keep original settlement steps out of the next Todo
2 parents dd0aafb + 9abb07d commit aedb376

7 files changed

Lines changed: 198 additions & 3 deletions

File tree

‎docs/architecture/rfcs/loopx-overall-roadmap-v0.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ P0 blocks correctness or continuity in the current user journey. P1 enables repe
7979
| **S4 Runtime/host/daemon · P0/P1** | Attached/managed, Turn, broker, runtime connectors and Desktop repairs exist; registration does not establish executable capacity | Qualify multi-Turn supervision for one real supported combination; restart/cancel/drain/stop retain work and fence old executors. Then expand host parity, unique service-profile ownership, clean installation and upgrades; show unsupported adapter capabilities |
8080
| **S5 Frontend, Lark and human interaction · P0/P1** | Local chat, settings, proposals and partial Goal Channel verticals exist; Goal Overview, Tasks, the W1 work map, usage, acceptance readback and lifecycle stop ship as separate regions without one page owner; shared audience/session/work readback needs qualification | One journey spans settings, work graph, handoff, blockers, cost, corrections, artifacts and return. **Page:** the [Goal workspace](goal-workspace-surface-v0.md) composes that journey per Goal over shared typed projections: a persistent masthead with scoped Stop, vitals with one meaning per figure (named window and coverage, unknown is not zero), work grouped by owner, results and an all-work rail. M1–M2 come first, after first-screen preview approval. **Cases:** reconnect/repeated-click/stale/original-route. **Realtime IM** reuses Chat/Turn: isolate independent conversations on one listener, then qualify ordinary DM onboarding, explicit role selection, busy-session admission, provisional progress, media and exact permission callbacks through [the shared operational contract](capable-manager-semantic-handoff-v0.md#10-operational-contract). **Team:** [Live team workspace](live-team-workspace-v0.md) makes exchange, revision and original-coordinator continuation visible. Its [Work-scale map track](live-team-workspace-v0.md#11-delivery-order-and-relationship-to-aggressive-r2-progress) draws each Goal's typed Todo relations first (W1, the Goal workspace's Map lens), then live state and outputs on the same nodes. Then intelligent review, keyboard accessibility and a launcher-only command palette, bilingual terminology, actionable errors and offline degradation; interrupt only for actual decisions |
8181
| **S6 Materials, evidence, memory and learning · P1** | Authority registry, material lifecycle/frontier, decision context, reward memory and turn recall exist; explicit project material packets and source-verifier gates are implemented, while private source initialization/Core binding and ordinary-conversation adoption remain unqualified; direction baseline and parts of attribution remain proposed | Connect material revision→same-Agent read→decision reference→artifact/outcome. Expose expiry/revocation/source loss and forgetting policy. Handoff preserves decision-relevant summaries and authorized artifacts; qualify OpenViking/Obelisk as optional providers. Prove causal utility with controls, not relevance alone |
82-
| **S7 Budget, scheduling and fleet scale · P0 observation/P1–P2 expansion** | Quota/scheduler and partial usage aggregates exist; full provider cost, distributed reservations and hundred-Agent concurrency need evidence | Separate configured budget, admission, consumption and estimates; unknown is not zero and replay cannot double-charge. R7 pagination/bounded summaries and [complete-history transport](typescript-control-plane-migration-v0.md), including refresh/replay/single-debit evidence beyond the RPC limit; provider/host limits, fairness, backpressure, event wake and isolation; report registration/activity/throughput and cost per accepted outcome separately. Receipt-backed semantic progress closes its original host Turn without completing an open/waiting Todo; File/SQLite guard replay preserves verified writeback when a later dependency wait changes the frontier and covers automatic quiet writeback during a receipt-owned retry, preserving independent work and monitor clocks. [Full quota and TurnEnvelope](../../reference/protocols/turn-envelope-v0.md) retain the original executable settlement command and single debit. Follow [the quota settlement contract](../../quota-allocation.md#receipt-backed-settlement-progress). Cross-Todo continuation isolation and live host backoff/readback remain separate acceptance; this bounded repair does not qualify fleet scale or host latency. |
82+
| **S7 Budget, scheduling and fleet scale · P0 observation/P1–P2 expansion** | Quota/scheduler and partial usage aggregates exist; full provider cost, distributed reservations and hundred-Agent concurrency need evidence | Separate configured budget, admission, consumption and estimates; unknown is not zero and replay cannot double-charge. R7 pagination/bounded summaries and [complete-history transport](typescript-control-plane-migration-v0.md), including refresh/replay/single-debit evidence beyond the RPC limit; provider/host limits, fairness, backpressure, event wake and isolation; report registration/activity/throughput and cost per accepted outcome separately. Receipt-backed semantic progress closes its original host Turn without completing an open/waiting Todo; File/SQLite guard replay preserves verified writeback when a later dependency wait changes the frontier and covers automatic quiet writeback during a receipt-owned retry, preserving independent work and monitor clocks. [Full quota and TurnEnvelope](../../reference/protocols/turn-envelope-v0.md) retain the original executable settlement command and single debit. Follow [the quota settlement contract](../../quota-allocation.md#receipt-backed-settlement-progress). File/SQLite CLI coverage also rejects an original Turn’s step write into a newly selected Todo, preserves that Todo’s step/CAS and dependencies, and settles the original writeback once. Installed adoption, live Host waiting-state cadence/readback and fleet scale remain separate acceptance. |
8383
| **S8 Capabilities, extensions and domain integration · P1/P2** | Capability catalog, extension lifecycle, hooks, engineering/research/content/office capabilities and computer-use contracts exist | First exercise the shared control plane with existing issue-fix/PR-review and material/research callers. Every provider has readiness/version/permissions/default-off/uninstall/rollback/isolation and real-entry evidence. New domain effects start with one simulated operation, not a marketplace or workflow DSL |
8484
| **S9 Identity, authority, privacy and trust · continuous P0/P1–P2 remote** | Public/private scope, capability gates, fencing and confirmation contracts belong to existing owners | R1/R3 cover sender/audience/artifact scope and stale authority; R6 authenticates tenant/Goal/actor/host, rotation/revocation and least privilege. Qualify credential custody, untrusted tool/document inputs, dependency supply chain, audit retention/deletion and vulnerability response through real paths; roles/messages/memory mint no write authority |
8585
| **S10 Reliability, diagnostics and operations · P0/P1** | Recovery/canary, read-only diagnostics prototype and DSH event adapter exist; C0/C1, overhead and full operations qualification are open | Failure classification→observable state→recovery drill→regression prevention; process/storage/network/delivery failures and data growth. Accepted Chat requests must settle even when runtime preparation fails before dispatch; qualify missing runtime assets, stop races and recovery without replay under [the shared conversation operational contract](capable-manager-semantic-handoff-v0.md#10-operational-contract). A context/provider read returning after the stop wait must honor the persisted Turn and exact Session claim: no late provider dispatch or handoff, even when a fresh request has completed. This bounded GQ08 repair does not certify upstream interrupt fidelity or effects already admitted elsewhere. Use [bounded repair lookup and targeted diagnostics](../../../skills/loopx-self-repair/references/targeted-diagnostics.md) to reduce redundant reads above the provider boundary; measure backend-specific cold/warm reads, writes and lock waits separately. Freeze SLO/RPO/RTO/capacity/retention boundaries and measure before qualification. Lark transport backpressure and drain retain the App consumer lease; unpersisted buffered lines are not accepted work or completion receipts. Runbooks include upgrade, restore, stop and human takeover; test counts do not prove recovery |

‎docs/reference/protocols/turn-envelope-v0.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,23 @@ ineligible/rejected choice still replays the receipt without mutation; a
322322
first-call rejection reports
323323
`heartbeat_receipt.status=not_committed` and writes no receipt event.
324324

325+
A receipt-bound `refresh-state --next-action` must refine that original Turn's
326+
Goal, Agent and Todo. If waiting or completion makes the current selector choose
327+
another Todo, the existing typed recommendation owner rejects the step before
328+
appending a run; even the other Todo's fresh read basis cannot retarget the
329+
original writeback. Finish the original writeback without `--next-action`, then
330+
select subsequent work through a fresh guard. A matching bound step records
331+
`settlement_alignment=exact`; ordinary unbound steps retain `not_applicable`.
332+
This changes the previous acceptance of a mismatched task-step write, without
333+
clearing dependencies, altering Todo authority or granting another Turn.
334+
335+
中文:绑定回执的 `refresh-state --next-action` 必须属于原 Turn 的 Goal、Agent
336+
和 Todo。原任务等待或完成后,即使当前选择器已选中另一个 Todo,且提供其最新
337+
read basis,同一 TS recommendation owner 仍在追加 run 前拒绝跨任务步骤。
338+
先去掉 `--next-action` 完成原写回,再通过 fresh guard 选择后续工作。匹配步骤保留
339+
`settlement_alignment=exact`,无 Turn 绑定的普通步骤仍为 `not_applicable`。
340+
本项修复此前错误接受的跨任务写入,不清依赖、不改变 Todo 权威或授予新 Turn。
341+
325342
For an eligible explicit choice deferred solely by runnable autonomous replan,
326343
the typed qualifier adds `inline_reentry_allowed=true`. After durably retaining
327344
that choice, the CLI now executes one same-Turn guard reentry and returns its

‎loopx/control_plane/work_items/recommendation_source_io.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Source I/O for the existing TS recommendation owner; no task write authority."""
22
from __future__ import annotations
33

4-
from collections.abc import Iterator
4+
from collections.abc import Iterator, Mapping
55
from contextlib import ExitStack, contextmanager
66
from pathlib import Path
77
from typing import Any
@@ -76,6 +76,7 @@ def latest_bound_recommendation(runs: list[dict[str, Any]], agent_id: str) -> di
7676
def lane_recommendation_context(source: dict[str, Any], *, agent_id: str | None,
7777
selected_todo: dict[str, Any] | None, task: dict[str, Any] | None,
7878
prior_resolution: dict[str, Any] | None = None, write: dict[str, Any] | None = None,
79+
settlement_identity: Mapping[str, Any] | None = None,
7980
) -> dict[str, Any]:
8081
# Reuse the existing frontier's semantic fact vocabulary. Display indexes,
8182
# selection reasons and bounded text decorations are not task revisions.
@@ -85,6 +86,7 @@ def lane_recommendation_context(source: dict[str, Any], *, agent_id: str | None,
8586
result = effect_runtime_result("work_item.refresh_recommendation.lane", {
8687
**source, "agent_id": agent_id, "selected_todo": selected_todo,
8788
"task_facts": facts, "prior_resolution": prior_resolution, "write": write,
89+
"settlement_identity": dict(settlement_identity) if settlement_identity is not None else None,
8890
})
8991
except EffectRuntimeRejected as error:
9092
raise ValueError(str(error)) from error

‎loopx/control_plane/work_items/refresh_recommendation.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def resolve_refresh_recommendation(
216216
context = lane_recommendation_context(source_context, agent_id=agent_id,
217217
selected_todo=selected, task=task,
218218
prior_resolution=latest_bound_recommendation(runs or [], agent_id),
219+
settlement_identity=settlement_identity,
219220
write={"text": next_step, "expected_basis": next_step_basis} if next_step else None)
220221
if next_step:
221222
resolved = context["resolution"]

‎loopx/control_plane/work_items/refresh_recommendation.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,23 @@ export function resolveLaneRecommendation(value: unknown): JsonObject {
112112
if (!actor || !agents.includes(actor)) {
113113
return reject("next_action_actor_unregistered", "Next Action requires a registered --agent-id");
114114
}
115+
// The current selector may legitimately choose B after receipt-bound A
116+
// starts waiting or completes. A's writeback cannot create B's step receipt,
117+
// even with a fresh B basis. Ordinary writes have no settlement binding.
118+
const settlement = settlementIdentity(request.settlement_identity);
119+
if (settlement && (settlement.goal_id !== goalId || settlement.agent_id !== actor ||
120+
settlement.todo_id !== selected?.todo_id)) {
121+
return reject("next_action_task_unavailable",
122+
"Next Action does not match the original Turn's Goal, agent and Todo; finish the original Turn writeback without --next-action, then select the next Todo through a fresh guard");
123+
}
115124
if (expected && expected !== basis) {
116125
return reject("next_action_basis_conflict", "Next Action task or lane read basis changed; reread status and rejudge before retrying");
117126
}
118127
if (!runnable || !taskFacts || taskFacts.todo_id !== selected?.todo_id) {
119128
return reject("next_action_task_unavailable", "No selected eligible advancement Todo for this agent; update or select work through the existing Todo route");
120129
}
121130
const resolution: JsonObject = {
122-
...recommendation(text, "agent_lane_step", "agent_lane", "not_applicable", selected),
131+
...recommendation(text, "agent_lane_step", "agent_lane", settlement ? "exact" : "not_applicable", selected),
123132
goal_id: goalId, agent_id: actor, todo_basis: taskBasis, source_revision: sourceRevision,
124133
read_basis: basis, step_revision: digest([basis, text]),
125134
};

0 commit comments

Comments
 (0)