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
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,11 @@ projection/review semantics. Both historical v1 decision shapes retain read
compatibility, but cannot discharge a fresh enabled review. Frozen workers retain
their exact source. Pin implementation revisions for comparisons; do not
reinterpret old decisions. The policy seam can evolve when evidence warrants it, without maintaining speculative variants.
For an enabled periodic review, every settlement path now requires a current,
source-bound stride decision. Typed progress, successor and terminal results
remain valid evidence/outcomes, while a bare Todo successor transition cannot
stand in for the review. This closes the decision-recording gap; it does not
establish that the proposed action was adopted or improved the Goal.
Six effective work Turns remains the review cadence, not the size of a Todo,
experiment or route change. Matched continuation must qualify actual adoption,
target benefit and full cost before promotion or synergy claims.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,9 @@ revision、考虑的 finding 与适用/暂缓等理由,不建新图或第二
普通投影/复盘;两种历史 v1 决策保留原义读取兼容,不能完成新的启用策略复盘;
新提案使用 v2 数据格式,仍只有同一个可选策略,冻结 worker 保持确切源码。比较固定
实现 revision,不重释旧决定;有证据后迭代同一组件,避免维护假设性的策略变体。
启用策略的周期复盘到期后,任何结算路径都须留下绑定本次来源的 stride 决定。
类型化进展、后续任务和终止结果仍可作为证据与结果,但单独的 Todo 后续转换不能
代替复盘。这补齐决定记录闭环,不证明提案已被采用或提升了 Goal。
六个有效工作 Turn 仍是复盘周期,不是 Todo、实验或路线变化的大小。策略采用、
目标收益、完整成本和交互增益须通过匹配接续等实验验证后再作结论。

Expand Down
16 changes: 14 additions & 2 deletions docs/reference/protocols/goal-vision-replan-contract-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,15 @@ changed evidence basis requires reassessment under the original Turn identity,
not another quota debit. Disabled Explore adds no Explore assessment duty.

The typed owner validates shape, references and declared provenance before the
fresh Vision path exit; other source-specific progress, successor and terminal
exits remain unchanged. All attachment text counts toward the existing
fresh Vision path exit. When an enabled Goal reaches a periodic review, every
accepted exit must also record a current `agent_vision.stride_decision`: a new
progress observation, runnable successor, blocker or coverage-backed terminal
result cannot silently replace that decision. The typed outcome remains legal
and retains its own evidence gate. The decision references both the due
obligation and its delivered review context. A terminal review may attach it to
an evidence-linked `stop` path. A Todo successor transition alone cannot settle
the due review. Other replan sources retain their existing exit policies.
All attachment text counts toward the existing
1,800-character Vision budget. The live packet exposes field limits. Legacy
recorded declarations and old explicit off-mode attachments retain transport
compatibility; a newly enabled fresh-path proposal requires v2 action,
Expand All @@ -512,6 +519,11 @@ revision 与具体 finding,说明采用、暂缓、无适用证据或读取不
按原 Turn 身份重新审视;不开额外执行权或配额。机器校验提案结构与引用,不能证明
模型判断正确或已提升收益。两种历史 v1 决策保留原义读取兼容,但不能完成新的
启用策略复盘;数据格式更新不增加可选策略版本。
策略启用且周期复盘到期时,所有可接受出口都须记录绑定本次义务的
`agent_vision.stride_decision`。新进展、可执行后续任务、阻塞或有覆盖证据的终止
仍可作为结果,但不能代替复盘决定;决定须引用本次义务及交付的复盘上下文,
单独创建后续 Todo 也不能结算到期义务。
终止时可把决定附在有证据的 `stop` 路径上。其他 replan 来源沿用原有出口规则。

The same opt-in strategy covers ordinary engineering, organizing and maintenance.
`grow` expands a coherent accepted deliverable, including related integration,
Expand Down
12 changes: 8 additions & 4 deletions loopx/control_plane/goals/goal_amendment_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
from ..work_items.autonomous_replan_obligation import ensure_replan_novelty_policy


def _replan_readback(derived: Mapping[str, Any], inventory: list[dict[str, Any]], view: Mapping[str, Any]) -> list[dict[str, Any]]:
def _replan_readback(derived: Mapping[str, Any], inventory: list[dict[str, Any]], view: Mapping[str, Any],
*, stride_strategy: str = "off") -> list[dict[str, Any]]:
"""Transport the existing replan owner's decisions, never create an ACK."""
if view.get("current_lifetime") is not True or not isinstance(view.get("intent"), dict):
return []
Expand All @@ -39,9 +40,11 @@ def _replan_readback(derived: Mapping[str, Any], inventory: list[dict[str, Any]]
obligation = obligations[item["obligation_id"]]
for agent in item["bound_agent_ids"]:
ack = replan_successor_transition_ack(None, agent_id=agent,
replan_obligation=obligation, agent_todo_items=view.get("work_items") or [])
replan_obligation=obligation, agent_todo_items=view.get("work_items") or [],
stride_strategy=stride_strategy)
accepted = autonomous_replan_ack_satisfies_obligation(ack,
replan_obligation=obligation, acceptance_gaps=None)
replan_obligation=obligation, acceptance_gaps=None,
stride_strategy=stride_strategy)
rows.append({"obligation_id": item["obligation_id"], "agent_id": agent,
"state": "settled" if accepted else "pending",
"acknowledgement": ack if accepted else None,
Expand Down Expand Up @@ -105,7 +108,8 @@ def inspect() -> dict[str, Any]:
timeout=CANONICAL_AUTHORITY_WRITE_TIMEOUT_SECONDS)
if not isinstance(value, dict):
raise TypeError("Goal amendment readback is invalid")
value["replan_observations"] = _replan_readback(derived, obligations, value)
value["replan_observations"] = _replan_readback(derived, obligations, value,
stride_strategy=(goal.get("execution_profile") or {}).get("stride_strategy", "off"))
return value

if operation in {"propose", "commit", "claim"}:
Expand Down
9 changes: 9 additions & 0 deletions loopx/control_plane/goals/goal_frontier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1658,11 +1658,15 @@ def build_goal_frontier_projection_context_from_status(
agent_id=agent_id,
)
effective_replan_ack = latest_agent_replan_ack or projected_replan_ack
stride_strategy = ((project_asset or {}).get("execution_profile") or {}).get(
"stride_strategy", "off"
)
replan_transition_ack = replan_successor_transition_ack(
agent_todo_summary,
agent_id=agent_id,
replan_obligation=replan_obligation,
agent_todo_items=agent_todo_source_items,
stride_strategy=stride_strategy,
)
# Keep the validated history-obligation transition distinct from any
# successor frontier obligation derived below. Exact Turn settlement may
Expand All @@ -1677,6 +1681,7 @@ def build_goal_frontier_projection_context_from_status(
replan_obligation=replan_obligation,
acceptance_gaps=source_acceptance_gaps,
todo_succession_gap_open=todo_succession_gap_open,
stride_strategy=stride_strategy,
)
and autonomous_replan_ack_matches_frontier(
obligation_ack,
Expand Down Expand Up @@ -1714,6 +1719,7 @@ def build_goal_frontier_projection_context_from_status(
agent_id=agent_id,
replan_obligation=frontier_replan_obligation,
agent_todo_items=agent_todo_source_items,
stride_strategy=stride_strategy,
)
frontier_obligation_ack = frontier_transition_ack or effective_replan_ack
frontier_transition_candidate = {"obligation": frontier_replan_obligation, "ack": frontier_transition_ack}
Expand All @@ -1729,6 +1735,7 @@ def build_goal_frontier_projection_context_from_status(
source_ack = replan_successor_transition_ack(
agent_todo_summary, agent_id=agent_id, replan_obligation=source,
agent_todo_items=agent_todo_source_items,
stride_strategy=stride_strategy,
)
frontier_transition_candidate = {"obligation": source, "ack": source_ack}

Expand All @@ -1754,6 +1761,7 @@ def build_goal_frontier_projection_context_from_status(
replan_obligation=frontier_replan_obligation,
acceptance_gaps=source_acceptance_gaps,
todo_succession_gap_open=todo_succession_gap_open,
stride_strategy=stride_strategy,
)
and autonomous_replan_ack_matches_frontier(
frontier_obligation_ack,
Expand Down Expand Up @@ -1806,6 +1814,7 @@ def build_goal_frontier_projection_context_from_status(
return {
"schema_version": "goal_frontier_projection_context_v0",
"replan_obligation": replan_obligation,
"stride_strategy": stride_strategy,
"replan_scope": replan_scope,
"goal_frontier_projection": goal_frontier_projection,
"acceptance_gaps": acceptance_gaps,
Expand Down
11 changes: 11 additions & 0 deletions loopx/control_plane/goals/goal_frontier/ack_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from ...work_items.progress_observation import (
replan_obligation_trigger_checkpoints,
replan_obligation_trigger_kinds,
replan_successor_transition_allowed,
required_semantic_outcomes,
)
from ...work_items.autonomous_replan_obligation import ensure_replan_novelty_policy
Expand All @@ -40,11 +41,16 @@ def autonomous_replan_ack_satisfies_obligation(
replan_obligation: dict[str, Any] | None,
acceptance_gaps: list[dict[str, Any]] | None,
todo_succession_gap_open: bool = False,
stride_strategy: str = "off",
) -> bool:
"""Reject ACKs that miss the obligation's typed semantic outcome."""

if not autonomous_replan_ack_has_frontier_delta(ack):
return False
if ack.get("source") == "todo_replan_successor_transition" and not replan_successor_transition_allowed(
replan_obligation or {}, stride_strategy=stride_strategy,
):
return False
semantic_delta = ack.get("semantic_delta") if isinstance(ack, dict) else {}
obligation_id = str((replan_obligation or {}).get("obligation_id") or "").strip()
acknowledged_obligation_id = str(
Expand Down Expand Up @@ -75,6 +81,7 @@ def replan_successor_transition_ack(
agent_id: str | None,
replan_obligation: dict[str, Any] | None,
agent_todo_items: Iterable[dict[str, Any]] | None = None,
stride_strategy: str = "off",
) -> dict[str, Any] | None:
"""Project an exact runnable-successor Todo as the replan receipt.

Expand Down Expand Up @@ -151,6 +158,10 @@ def replan_successor_transition_ack(
)
if successor_binding is None:
return None
if not replan_successor_transition_allowed(
replan_obligation or {}, stride_strategy=stride_strategy,
):
return None
if trigger_checkpoints is None:
trigger_checkpoints = replan_obligation_trigger_checkpoints(replan_obligation or {})
semantic_delta = {
Expand Down
2 changes: 2 additions & 0 deletions loopx/control_plane/goals/vision_checkpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def prepare_vision_refresh(
existing_agent_vision: dict[str, Any] | None,
merge_patch: bool,
require_path_delta_for_durable_change: bool,
allow_terminal_stride_path: bool = False,
) -> dict[str, Any]:
"""Run the TS-owned Vision preflight before semantic-replan qualification."""

Expand All @@ -92,6 +93,7 @@ def prepare_vision_refresh(
"require_path_delta_for_durable_change": bool(
require_path_delta_for_durable_change
),
"allow_terminal_stride_path": allow_terminal_stride_path,
},
)
except EffectRuntimeRejected as exc:
Expand Down
10 changes: 8 additions & 2 deletions loopx/control_plane/goals/vision_checkpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ interface VisionRefreshPrepareRequest {
existing_agent_vision: JsonObject | null;
merge_patch: boolean;
require_path_delta_for_durable_change: boolean;
allow_terminal_stride_path: boolean;
}

interface VisionRefreshFinalizeRequest {
Expand Down Expand Up @@ -556,6 +557,8 @@ function decodePrepareRequest(request: JsonObject): VisionRefreshPrepareRequest
request.require_path_delta_for_durable_change,
"require_path_delta_for_durable_change",
),
allow_terminal_stride_path: request.allow_terminal_stride_path === undefined ? false
: requiredBoolean(request.allow_terminal_stride_path, "allow_terminal_stride_path"),
};
}

Expand Down Expand Up @@ -641,9 +644,12 @@ function prepareVisionRefresh(request: VisionRefreshPrepareRequest): JsonObject
const [pathDelta, pathDeltaUsage] = normalizeGoalPathDelta(updatePacket.path_delta);
Object.assign(fieldUsage, pathDeltaUsage);
const [strideDecision, strideUsage] = normalizeStrideDecision(updatePacket.stride_decision, boundedPublicText);
if (strideDecision && (!pathDelta || !["continue", "no_change", "replan"].includes(String(pathDelta.outcome)) ||
const allowedStridePaths = request.allow_terminal_stride_path
? ["continue", "no_change", "replan", "stop"]
: ["continue", "no_change", "replan"];
if (strideDecision && (!pathDelta || !allowedStridePaths.includes(String(pathDelta.outcome)) ||
!Array.isArray(pathDelta.evidence_refs) || !pathDelta.evidence_refs.length)) {
throw new EffectRuntimeRequestError("stride_decision requires a fresh evidence-linked path decision");
throw new EffectRuntimeRequestError("stride_decision requires an evidence-linked path decision");
}
Object.assign(fieldUsage, strideUsage);
const [fallbackDeclarations, fallbackUsage] = normalizeFallbackDeclarations(
Expand Down
8 changes: 6 additions & 2 deletions loopx/control_plane/host_adapter_settlement.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ def _request_payload(


@contextmanager
def host_vision_request(request: HostTodoSettlementRequest, vision: dict | None, unchanged: str):
def host_vision_request(
request: HostTodoSettlementRequest, vision: dict | None, unchanged: str,
*, allow_terminal_stride_path: bool = False,
):
"""Materialize authored JSON for the existing CLI codec, never as public state."""
if vision is not None and unchanged:
raise ValueError("choose a vision patch or an unchanged reason, not both")
Expand All @@ -129,7 +132,8 @@ def host_vision_request(request: HostTodoSettlementRequest, vision: dict | None,
# This is syntax/budget preflight only: refresh-state still validates against
# the real baseline and current replan/settlement state at writeback time.
prepare_vision_refresh(vision, goal_id=request.goal_id, agent_id=request.agent_id,
existing_agent_vision=None, merge_patch=False, require_path_delta_for_durable_change=False)
existing_agent_vision=None, merge_patch=False, require_path_delta_for_durable_change=False,
allow_terminal_stride_path=allow_terminal_stride_path)
with tempfile.TemporaryDirectory(prefix="loopx-host-vision-") as directory:
path = Path(directory) / "vision.json"
path.write_text(json.dumps(vision, ensure_ascii=False, allow_nan=False), encoding="utf-8")
Expand Down
3 changes: 3 additions & 0 deletions loopx/control_plane/quota/should_run_prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ def _preserve_receipt_bound_replan_obligation(
agent_id: str | None = None,
replay_phase: ReceiptBoundReplayPhase | None = None,
transition_candidates: list[dict[str, Any]] | None = None,
stride_strategy: str = "off",
) -> dict[str, Any] | None:
preserved_replan_id = normalize_todo_replan_obligation_id(
receipt_bound_replan_obligation_id
Expand All @@ -206,6 +207,7 @@ def _preserve_receipt_bound_replan_obligation(
"agent_id": agent_id,
"replay_phase": replay_phase.value if replay_phase is not None else None,
"transition_candidates": transition_candidates or [],
"stride_strategy": stride_strategy,
})
except EffectRuntimeRejected as exc:
if exc.diagnostic_code == "heartbeat_receipt_identity_conflict":
Expand Down Expand Up @@ -858,6 +860,7 @@ def _prepare_quota_should_run_item(
agent_id=agent_frontier_id,
replay_phase=receipt_bound_replay_phase,
transition_candidates=goal_frontier_context.get("replan_transition_candidates"),
stride_strategy=goal_frontier_context["stride_strategy"],
)
)
replan_scope = goal_frontier_context.get("replan_scope") or {}
Expand Down
21 changes: 19 additions & 2 deletions loopx/control_plane/work_items/progress_observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,16 +345,33 @@ def required_semantic_outcomes(obligation: Mapping[str, Any]) -> list[str]:
return list(replan_writeback_requirements(obligation)["required_any_of"])


def replan_successor_transition_allowed(
obligation: Mapping[str, Any], *, stride_strategy: str = "off",
) -> bool:
"""Ask the typed owner whether a Todo transition can settle this review."""
try:
result = effect_runtime_result("work_item.replan_semantics.project", {
"operation": "transition_eligibility", "obligation": dict(obligation),
"stride_strategy": stride_strategy,
})
except EffectRuntimeRejected as exc:
raise ValueError(str(exc)) from None
if not isinstance(result, Mapping) or not isinstance(result.get("allowed"), bool):
raise RuntimeError("TypeScript transition eligibility shape mismatch")
return result["allowed"]


def guarded_replan_transition_delta(
*, guard_scoped: bool, selected_obligation_id: str | None,
transition_acks: list[dict[str, Any] | None],
transition_candidates: list[dict[str, Any]], stride_strategy: str = "off",
) -> dict[str, Any] | None:
"""Adapt revalidated canonical receipts to the TS-owned exact Turn gate."""
try:
result = effect_runtime_result("work_item.replan_semantics.project", {
"operation": "turn_transition", "guard_scoped": guard_scoped,
"selected_obligation_id": selected_obligation_id,
"transition_acks": transition_acks,
"transition_candidates": transition_candidates,
"stride_strategy": stride_strategy,
})
except EffectRuntimeRejected as exc:
raise ValueError(str(exc)) from None
Expand Down
Loading
Loading