Skip to content

Commit 57356aa

Browse files
committed
fix(control-plane): require outcome-qualified replans
1 parent db1825b commit 57356aa

10 files changed

Lines changed: 845 additions & 57 deletions

File tree

docs/reference/protocols/goal-vision-replan-contract-v0.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,25 @@ For the same `agent_id`, a newer satisfied checkpoint with `patched`,
191191
`unchanged_with_reason`, or `retired_or_superseded` supersedes older
192192
`missing_required` checkpoints; `not_required` does not.
193193

194+
A satisfied checkpoint is protocol-complete, but a material closeout also has
195+
to qualify its relationship to the final outcome. For an open vision, the same
196+
refresh must name the active `acceptance_summary`, attach public-safe
197+
`goal_path_delta_v0.evidence_refs`, and record one of these decisions:
198+
199+
- `continue` or `no_change` when the new evidence supports the final-outcome
200+
path and the delivery did not report `outcome_gap`; or
201+
- `replan` when the evidence contradicts or leaves the path open and the same
202+
refresh records an autonomous replan with a real frontier delta.
203+
204+
An older path delta, an unchanged-with-reason decision, or an unrelated
205+
runnable todo does not qualify the material closeout. Quota projects
206+
`vision_outcome_checkpoint_required` ahead of ordinary runnable work until a
207+
fresh evidence-linked continuation or replan is recorded. The same rule
208+
applies when a same-agent advancement todo was completed after the latest
209+
qualifying checkpoint. Todo completion is therefore the checkpoint timing
210+
signal, not proof that the final acceptance contract is done; the evidence
211+
decides whether to continue, replan/supersede, or close.
212+
194213
Checkpoint and autonomous-replan ACK packets are protocol records, not semantic
195214
completion proof. A future monitor schedule is also not completion proof; it
196215
only says when to poll. A recent same-agent ACK may suppress duplicate

loopx/control_plane/goals/goal_frontier/__init__.py

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
goal_vision_state_requires_successor,
2727
)
2828
from ..goal_vision_wait import build_goal_vision_wait_state
29+
from . import outcome_continuity
2930
from .replan_rules import (
3031
GoalFrontierReplanFacts,
3132
GoalFrontierReplanRule,
@@ -47,16 +48,14 @@
4748
AUTONOMOUS_REPLAN_SCOPE_SCHEMA_VERSION = "autonomous_replan_scope_v0"
4849
AUTONOMOUS_REPLAN_OBLIGATION_SCHEMA_VERSION = "autonomous_replan_obligation_v0"
4950
REPEAT_VISION_REPLAN_SATISFYING_DELTA_KINDS = (
50-
"runnable_todo_set",
51-
"successor_or_supersede",
51+
outcome_continuity.REPEAT_VISION_REPLAN_SATISFYING_DELTA_KINDS
5252
)
5353
AUTONOMOUS_REPLAN_REQUIRED_MODE = "autonomous_replan_required"
5454
FRONTIER_EXHAUSTED_MONITOR_TRIGGER = "frontier_exhausted_monitor_lane"
5555
MONITOR_NO_CHANGE_STREAK_TRIGGER = "monitor_no_change_streak"
5656
LONG_TODO_CHAIN_TRIGGER = "long_todo_chain"
5757
VISION_ACCEPTANCE_GAP_TRIGGER = "vision_acceptance_gap"
5858
VISION_SUCCESSOR_GAP_TRIGGER = "vision_successor_required"
59-
VISION_CHECKPOINT_MISSING_TRIGGER = "vision_checkpoint_missing"
6059
VISION_PROFILE_MISSING_TRIGGER = "required_agent_vision_missing"
6160
TODO_SUCCESSION_GAP_TRIGGER = "completed_advancement_without_successor"
6261
TODO_TASK_CLASS_ADVANCEMENT = "advancement_task"
@@ -724,48 +723,6 @@ def acceptance_gaps_from_agent_profile_requirement(
724723
]
725724

726725

727-
def acceptance_gaps_from_vision_checkpoint(
728-
checkpoint: dict[str, Any] | None,
729-
) -> list[dict[str, Any]]:
730-
"""Convert a missing per-agent vision checkpoint into a frontier gap."""
731-
732-
if not isinstance(checkpoint, dict):
733-
return []
734-
trigger_kinds = [
735-
str(trigger.get("kind") or "").strip()
736-
for trigger in (checkpoint.get("triggers") or [])
737-
if isinstance(trigger, dict) and str(trigger.get("kind") or "").strip()
738-
]
739-
trigger_text = ", ".join(trigger_kinds[:3]) or "required vision checkpoint"
740-
missing_baseline = checkpoint.get("missing_baseline") is True
741-
gap: dict[str, Any] = {
742-
"kind": VISION_CHECKPOINT_MISSING_TRIGGER,
743-
"source": "latest_vision_checkpoint",
744-
"agent_id": checkpoint.get("agent_id"),
745-
"decision": checkpoint.get("decision"),
746-
"replan_trigger_summary": (
747-
"refresh-state tried to keep vision unchanged without a persisted "
748-
f"per-agent baseline; triggers={trigger_text}"
749-
if missing_baseline
750-
else "refresh-state closed a material segment without a per-agent vision "
751-
f"decision; triggers={trigger_text}"
752-
),
753-
"acceptance_summary": (
754-
"Write a bounded agent vision patch, or retire/supersede the frontier "
755-
"with an explicit rationale."
756-
if missing_baseline
757-
else "Write a bounded agent vision patch, record an unchanged reason, "
758-
"or retire/supersede the frontier with an explicit rationale."
759-
),
760-
}
761-
if missing_baseline:
762-
gap["missing_baseline"] = True
763-
generated_at = _compact_projection_text(checkpoint.get("generated_at"), limit=80)
764-
if generated_at:
765-
gap["generated_at"] = generated_at
766-
return [gap]
767-
768-
769726
def build_vision_continuation_audit(
770727
*,
771728
goal_id: str | None = None,
@@ -1373,6 +1330,11 @@ def derive_goal_frontier_replan_obligation_from_summaries(
13731330
in {VISION_SUCCESSOR_GAP_TRIGGER, VISION_PROFILE_MISSING_TRIGGER}
13741331
for item in compact_acceptance_gaps
13751332
)
1333+
outcome_checkpoint_replan_required = any(
1334+
item.get("kind")
1335+
== outcome_continuity.VISION_OUTCOME_CHECKPOINT_REQUIRED_TRIGGER
1336+
for item in compact_acceptance_gaps
1337+
)
13761338
acceptance_allows_watch_lane_continuation = bool(
13771339
compact_acceptance_gaps
13781340
and not any(
@@ -1418,6 +1380,9 @@ def derive_goal_frontier_replan_obligation_from_summaries(
14181380
total_frontier_advancement=total_frontier_advancement,
14191381
acceptance_gap_count=len(compact_acceptance_gaps),
14201382
selectable_frontier_advancement=selectable_frontier_advancement,
1383+
outcome_checkpoint_replan_required=(
1384+
outcome_checkpoint_replan_required
1385+
),
14211386
acceptance_allows_watch_lane_continuation=(
14221387
acceptance_allows_watch_lane_continuation
14231388
),
@@ -1785,6 +1750,13 @@ def build_goal_frontier_projection_context_from_status(
17851750
goal_id=goal_id,
17861751
agent_id=agent_id,
17871752
)
1753+
latest_vision_checkpoint = (
1754+
outcome_continuity.latest_outcome_vision_checkpoint_from_status_payload(
1755+
status_payload,
1756+
goal_id=goal_id,
1757+
agent_id=agent_id,
1758+
)
1759+
)
17881760
source_acceptance_gaps = (
17891761
acceptance_gaps_from_agent_profile_requirement(
17901762
agent_profile,
@@ -1796,7 +1768,19 @@ def build_goal_frontier_projection_context_from_status(
17961768
latest_agent_vision,
17971769
goal_status=goal_status,
17981770
)
1799-
+ acceptance_gaps_from_vision_checkpoint(latest_missing_vision_checkpoint)
1771+
+ outcome_continuity.acceptance_gaps_from_vision_checkpoint(
1772+
latest_missing_vision_checkpoint
1773+
)
1774+
+ outcome_continuity.acceptance_gaps_from_outcome_checkpoint(
1775+
latest_agent_vision,
1776+
latest_vision_checkpoint,
1777+
)
1778+
+ outcome_continuity.acceptance_gaps_from_todo_completion_checkpoint(
1779+
latest_agent_vision,
1780+
latest_vision_checkpoint,
1781+
agent_todo_summary=agent_todo_summary,
1782+
agent_id=agent_id,
1783+
)
18001784
)
18011785
if _terminal_no_followup_resolves_vision_checkpoint(
18021786
user_todo_summary=user_todo_summary,
@@ -1806,7 +1790,8 @@ def build_goal_frontier_projection_context_from_status(
18061790
source_acceptance_gaps = [
18071791
gap
18081792
for gap in source_acceptance_gaps
1809-
if gap.get("kind") != VISION_CHECKPOINT_MISSING_TRIGGER
1793+
if gap.get("kind")
1794+
!= outcome_continuity.VISION_CHECKPOINT_MISSING_TRIGGER
18101795
]
18111796
replan_obligation = align_autonomous_replan_guidance_with_acceptance_policy(
18121797
replan_obligation,

0 commit comments

Comments
 (0)