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
12 changes: 12 additions & 0 deletions docs/architecture/rfcs/typescript-control-plane-migration-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,18 @@ reduction remains a measured follow-up with complete-source parity, not a second
Python policy. See the
[history decision evidence](ledger/typescript-control-plane-migration-v0/2026-09-22-replan-history-policy.md).

Settlement wait adapters now share the existing resume-fact projection over the
complete dependency graph, retaining the bound Todo's resume proof and Monitor
clocks, hash and cadence. They do not retransmit unrelated Todo prose or truncate
canonical records. Blocked writeback, receipt-bound wait recovery and ordinary
`quota.settlement.read` readback explicitly reuse the same-UID, digest-bound
private snapshot channel when needed; the 2 MiB wire and 64 MiB snapshot limits
remain unchanged. The existing TS owner still qualifies dependencies, frozen
observations, original Turn identity, blocked closeout without spending and
once-only accounting. Isolated File/SQLite CLI validation covers a complete
multi-MiB Goal and replay; source validation does not establish installed-host
adoption or whole-Goal qualification.

**Checkpoint read-context transport.** A long-lived Goal can exceed the Effect
runtime's 2 MiB socket limit even when the original Turn receipt is small:
complete shared Goal prose and archived Todo facts are part of the read basis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,14 @@ File/SQLite CLI 恢复覆盖超过 wire 预算的完整事实、后续独立工
游标/checkpoint 归约保留为以测量驱动、完整源语义一致
为前提的后续工作,不再造 Python 规则。见[历史决策证据](ledger/typescript-control-plane-migration-v0/2026-09-22-replan-history-policy.zh-CN.md)。

结算等待适配器现在对完整依赖图共用既有 resume 事实投影,保留绑定 Todo 的
resume 证明及 Monitor 时钟、hash、cadence;不重复传递无关正文,也不截断
canonical 记录。受阻 writeback、回执绑定等待恢复和普通 `quota.settlement.read`
回读按需显式复用同 UID、摘要绑定的私有 snapshot 通道,2 MiB wire 和 64 MiB
snapshot 上限均不变。依赖资格、冻结观测、原 Turn 身份、受阻不扣额收尾及
精确一次记账仍由既有 TS owner 判断。隔离 File/SQLite CLI 验证覆盖完整多 MiB
Goal 与重放;源码验证不代表已安装宿主采用或整 Goal 资格通过。

**Checkpoint 恢复编写契约。** 现有 TS vision owner 只能投影校验器接受的恢复
选项:没有持久 vision 时,缺失的 checkpoint 必须提交 vision patch;有该基线时
才提供 unchanged reason。Authoring 契约复用校验器的 `todo_delta` 限制:保留前
Expand Down
1 change: 1 addition & 0 deletions loopx/control_plane/effect_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"todo.context.page",
"work_item.context.project",
"work_item.replan_semantics.project",
"quota.settlement.read",
"goal.checkpoint_read_context.resolve",
"goal.checkpoint_read_context.commit",
"goal.checkpoint_read_context.inspect_replay",
Expand Down
1 change: 1 addition & 0 deletions loopx/control_plane/effect_runtime_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const LOCAL_SNAPSHOT_METHODS = new Set([
"todo.context.page",
"work_item.context.project",
"work_item.replan_semantics.project",
"quota.settlement.read",
"goal.checkpoint_read_context.resolve",
"goal.checkpoint_read_context.commit",
"goal.checkpoint_read_context.inspect_replay",
Expand Down
25 changes: 23 additions & 2 deletions loopx/control_plane/quota/blocked_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any

from ..todos.contract import normalize_todo_id
from ..todos.resume_condition import compact_todo_resume_items
from ..effect_runtime import EffectRuntimeRejected, effect_runtime_result

BLOCKED_RETRY_SCHEMA_VERSION = "quota_blocked_retry_v0"
Expand All @@ -14,6 +15,26 @@
TURN_SETTLEMENT_RETRY_SECONDS = 5 * 60


def compact_settlement_wait_todos(
items: list[dict[str, Any]], *, todo_id: str,
) -> list[dict[str, Any]]:
"""Transport all dependency facts and the bound wait's frozen observation.

The TS blocked-wait owner needs the complete graph for cycle diagnosis,
but only the bound Todo's resume proof and Monitor clocks. Keep those
observations verbatim; prose and completion instructions are not inputs.
"""
facts = compact_todo_resume_items(items)
bound_fields = (
"resume_condition", "last_checked_at", "next_due_at", "result_hash",
"material_change", "cadence", "expires_at", "watch_only",
)
for source, row in zip((item for item in items if item.get("todo_id")), facts):
if source.get("todo_id") == todo_id:
row.update({field: source[field] for field in bound_fields if field in source})
return facts


def require_blocked_retry_wait(
todo_fields: dict[str, Any] | None,
*,
Expand All @@ -30,11 +51,11 @@ def require_blocked_retry_wait(
try:
result = effect_runtime_result("quota.settlement.read", {
"schema_version": "loopx_quota_blocked_wait_request_v0",
"todos": items,
"todos": compact_settlement_wait_todos(items, todo_id=todo_id),
"todo_id": todo_id,
"observed_at": observed_at,
"allow_turn_settlement_retry": allow_turn_settlement_retry,
})
}, large_local_snapshot=True)
except EffectRuntimeRejected as exc:
raise ValueError(str(exc)) from exc
if not isinstance(result, dict):
Expand Down
1 change: 1 addition & 0 deletions loopx/control_plane/quota/settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ def read(admission: Mapping[str, Any] | None) -> Any:
else {}
),
},
large_local_snapshot=True,
)
except EffectRuntimeRejected as exc:
raise ValueError(str(exc)) from None
Expand Down
10 changes: 3 additions & 7 deletions loopx/control_plane/quota/unsettled_host_turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,26 +362,22 @@ def apply_receipt_bound_wait_recovery(
) -> bool:
"""Read full provider facts only when a replay lost its executable binding."""
from ..todos.list_readback import list_goal_todos
from ..todos.resume_condition import compact_todo_resume_items
from .blocked_retry import compact_settlement_wait_todos
from ..runtime.time import now_utc_iso

source = list_goal_todos(
registry_path=registry_path, runtime_root_arg=str(runtime_root), goal_id=goal_id,
)
# Keep transitive dependencies from this coherent read. A direct-target
# slice loses longer cycles; compact facts keep prose out of the RPC.
bound: dict[str, Any] = next((row for row in source["todos"] if row.get("todo_id") == todo_id), {})
items = compact_todo_resume_items(source["todos"])
for row in items:
if row.get("todo_id") == todo_id:
row["resume_condition"] = bound.get("resume_condition")
items = compact_settlement_wait_todos(source["todos"], todo_id=todo_id)
verdict = effect_runtime_result("quota.settlement.read", {
"schema_version": "loopx_quota_receipt_bound_wait_request_v0",
"todos": items, "todo_id": todo_id, "agent_id": agent_id,
"turn_instance_id": turn_instance_id, "observed_at": now_utc_iso(),
"monitor_phase": monitor_phase,
"settlement_progress": settlement_progress,
})
}, large_local_snapshot=True)
if verdict.get("status") == "none":
return False
if verdict.get("status") != "recovery_required":
Expand Down
Loading
Loading