11from __future__ import annotations
22
3- import hashlib
4- import json
53from typing import Any
64
75from ...agents .agent_scope import (
5755)
5856from ..goal_vision_wait import build_goal_vision_wait_state
5957from . import outcome_continuity
58+ from .acceptance import (
59+ GOAL_ACCEPTANCE_HOLD_TRIGGERS ,
60+ acceptance_gaps_from_held_goal_binding ,
61+ )
6062from .ack_policy import (
6163 autonomous_replan_ack_satisfies_obligation ,
6264 replan_successor_transition_ack ,
106108FRONTIER_EXHAUSTED_MONITOR_TRIGGER = "frontier_exhausted_monitor_lane"
107109MONITOR_NO_CHANGE_STREAK_TRIGGER = "monitor_no_change_streak"
108110VISION_PROFILE_MISSING_TRIGGER = "required_agent_vision_missing"
109- GOAL_ACCEPTANCE_STALE_TRIGGER = "goal_acceptance_stale"
110111TODO_SUCCESSION_GAP_TRIGGER = TODO_SUCCESSION_WARNING_REASON_CODE
111112
112113
@@ -368,69 +369,6 @@ def acceptance_gaps_from_agent_profile_requirement(
368369 ]
369370
370371
371- def acceptance_gaps_from_stale_goal_binding (
372- agent_todo_summary : dict [str , Any ] | None ,
373- source_items : list [dict [str , Any ]] | None ,
374- * ,
375- agent_id : str | None ,
376- ) -> list [dict [str , Any ]]:
377- """Route this agent's held semantic drift through the existing replan lane.
378-
379- This is a read-only trigger, not an acceptance rebind. Other runnable work
380- remains selectable; the frontier rule schedules a replan only when no
381- advancement Todo can be selected for this agent.
382- """
383-
384- if not agent_id or not isinstance (agent_todo_summary , dict ):
385- return []
386- contract = agent_todo_summary .get ("goal_acceptance_contract" )
387- if not isinstance (contract , dict ) or contract .get ("enabled" ) is not True :
388- return []
389- stale_ids = {
390- task .get ("todo_id" )
391- for task in contract .get ("tasks" , [])
392- if isinstance (task , dict )
393- and task .get ("state" ) == "stale"
394- and task .get ("applicable" ) is True
395- }
396- gaps : list [dict [str , Any ]] = []
397- for item in source_items or []:
398- if (
399- not isinstance (item , dict )
400- or item .get ("todo_id" ) not in stale_ids
401- or item .get ("role" ) != "agent"
402- or item .get ("status" ) not in {"open" , "blocked" }
403- or not agent_scope_item_claimed_by_agent_or_unclaimed (item , agent_id = agent_id )
404- ):
405- continue
406- todo_id = str (item ["todo_id" ])
407- frontier_revision = hashlib .sha256 (json .dumps (
408- [todo_id , item .get ("updated_at" ), contract .get ("digest" )],
409- ensure_ascii = True , separators = ("," , ":" ), default = str ,
410- ).encode ("utf-8" )).hexdigest ()
411- gap = {
412- "kind" : GOAL_ACCEPTANCE_STALE_TRIGGER ,
413- "source" : "goal_acceptance_contract" ,
414- "agent_id" : agent_id ,
415- "reason_code" : GOAL_ACCEPTANCE_STALE_TRIGGER ,
416- "vision_todo_ids" : [todo_id ],
417- "frontier_revision" : frontier_revision ,
418- "replan_trigger_summary" : f"The acceptance association for { todo_id } is stale after a work change." ,
419- "acceptance_summary" : "Preserve the owner-confirmed criteria and the original Turn identity." ,
420- "resolution_hint" : (
421- f"Inspect { todo_id } and its acceptance binding; restore an unintended edit, "
422- "or record an evidence-linked path delta and continue via an eligible "
423- "successor. Escalate only a real change to owner-owned criteria or scope; "
424- "never rebind or settle a different Todo under the original Turn."
425- ),
426- }
427- if isinstance (item .get ("updated_at" ), str ):
428- gap ["generated_at" ] = item ["updated_at" ]
429- gaps .append (gap )
430- if len (gaps ) == 3 :
431- break
432- return gaps
433-
434372
435373def build_vision_continuation_audit (
436374 * ,
@@ -991,11 +929,11 @@ def _vision_gap_acknowledged(
991929 # The vision-patch shortcut below covers gaps authored by that same Turn.
992930 # A persisted Goal Acceptance drift is an independent Todo event: an older
993931 # vision patch cannot acknowledge a later semantic edit.
994- stale_bindings = [
932+ held_bindings = [
995933 gap for gap in acceptance_gaps
996- if gap .get ("kind" ) == GOAL_ACCEPTANCE_STALE_TRIGGER
934+ if gap .get ("kind" ) in GOAL_ACCEPTANCE_HOLD_TRIGGERS
997935 ]
998- if stale_bindings :
936+ if held_bindings :
999937 semantic_delta = latest_replan_ack .get ("semantic_delta" )
1000938 satisfying_outcomes = (
1001939 semantic_delta .get ("satisfying_outcomes" )
@@ -1009,24 +947,25 @@ def _vision_gap_acknowledged(
1009947 and isinstance (semantic_delta .get ("trigger_checkpoints" ), list )
1010948 else []
1011949 )
1012- exact_stale_checkpoints = all (
950+ exact_hold_checkpoints = all (
1013951 any (
1014952 isinstance (checkpoint , dict )
1015- and checkpoint .get ("kind" ) == GOAL_ACCEPTANCE_STALE_TRIGGER
953+ and checkpoint .get ("kind" ) == gap . get ( "kind" )
1016954 and checkpoint .get ("frontier_revision" ) == gap .get ("frontier_revision" )
1017955 for checkpoint in recorded_checkpoints
1018956 )
1019- for gap in stale_bindings
957+ for gap in held_bindings
1020958 )
1021959 if (
1022960 not _replan_evidence_acknowledged (
1023- stale_bindings , latest_replan_ack , time_key = "generated_at" ,
961+ held_bindings , latest_replan_ack , time_key = "generated_at" ,
1024962 )
1025963 or not isinstance (semantic_delta , dict )
1026964 or latest_replan_ack .get ("recorded" ) is not True
1027965 or semantic_delta .get ("accepted" ) is not True
1028- or GOAL_ACCEPTANCE_STALE_TRIGGER not in (semantic_delta .get ("trigger_kinds" ) or [])
1029- or not exact_stale_checkpoints
966+ or not all (gap .get ("kind" ) in (semantic_delta .get ("trigger_kinds" ) or [])
967+ for gap in held_bindings )
968+ or not exact_hold_checkpoints
1030969 or not any (
1031970 outcome in {"new_runnable_successor" , "new_concrete_blocker" }
1032971 for outcome in satisfying_outcomes if isinstance (outcome , str )
@@ -1105,9 +1044,12 @@ def derive_goal_frontier_replan_obligation_from_summaries(
11051044 if selectable_frontier_advancement == 0
11061045 else None
11071046 )
1108- compact_acceptance_gaps = [
1109- item for item in (acceptance_gaps or []) if isinstance (item , dict )
1110- ]
1047+ compact_acceptance_gaps = sorted (
1048+ (item for item in (acceptance_gaps or []) if isinstance (item , dict )),
1049+ # An enforced hold must survive the bounded trigger projection. Its
1050+ # exact checkpoint is required to settle; generic vision gaps follow.
1051+ key = lambda gap : gap .get ("kind" ) not in GOAL_ACCEPTANCE_HOLD_TRIGGERS ,
1052+ )
11111053 if any (gap .get ("vision_todo_ids" ) for gap in compact_acceptance_gaps ):
11121054 # Diagnostic claim counts retain executor-excluded work. A causal
11131055 # acceptance obligation needs an actually selectable Todo identity.
@@ -1268,6 +1210,9 @@ def derive_goal_frontier_replan_obligation_from_summaries(
12681210 },
12691211 )
12701212 if replan_rule .rule is GoalFrontierReplanRule .VISION_ACCEPTANCE_GAP :
1213+ acceptance_held = any (
1214+ gap .get ("kind" ) in GOAL_ACCEPTANCE_HOLD_TRIGGERS for gap in compact_acceptance_gaps
1215+ )
12711216 rearmed_after_obligation_id = _acknowledged_replan_obligation_id (
12721217 latest_replan_ack
12731218 )
@@ -1305,13 +1250,14 @@ def derive_goal_frontier_replan_obligation_from_summaries(
13051250 }
13061251 for gap in compact_acceptance_gaps [:3 ]
13071252 ],
1308- guidance_actions = [
1309- "create_successor" ,
1310- "update_agent_vision" ,
1311- "record_evidence_gap" ,
1312- "record_no_followup" ,
1313- ],
1314- todo_actions = [
1253+ guidance_actions = (
1254+ ["inspect_acceptance_binding" , "select_eligible_successor" , "record_evidence_gap" ]
1255+ if acceptance_held else
1256+ ["create_successor" , "update_agent_vision" , "record_evidence_gap" , "record_no_followup" ]
1257+ ),
1258+ # Another unbound advancement Todo cannot repair this admission
1259+ # hold. Keep authoring suggestions aligned with the typed exits.
1260+ todo_actions = [] if acceptance_held else [
13151261 {
13161262 "action" : "add" ,
13171263 "role" : "agent" ,
@@ -1339,11 +1285,6 @@ def derive_goal_frontier_replan_obligation_from_summaries(
13391285 "record no-follow-up"
13401286 ),
13411287 rearmed_after_obligation_id = rearmed_after_obligation_id ,
1342- extra_fields = (
1343- {"satisfying_semantic_outcomes" : ["new_runnable_successor" , "new_concrete_blocker" ]}
1344- if any (gap .get ("kind" ) == GOAL_ACCEPTANCE_STALE_TRIGGER for gap in compact_acceptance_gaps )
1345- else None
1346- ),
13471288 )
13481289 if replan_rule .rule is GoalFrontierReplanRule .LONG_TODO_CHAIN :
13491290 assert long_chain_observation is not None
@@ -1641,7 +1582,7 @@ def build_goal_frontier_projection_context_from_status(
16411582 (project_asset or {}).get ("execution_profile" )
16421583 ),
16431584 )
1644- + acceptance_gaps_from_stale_goal_binding (
1585+ + acceptance_gaps_from_held_goal_binding (
16451586 agent_todo_summary , agent_todo_source_items , agent_id = agent_id ,
16461587 )
16471588 )
@@ -1681,7 +1622,7 @@ def build_goal_frontier_projection_context_from_status(
16811622 ),
16821623 )
16831624 acceptance_gaps = (
1684- [gap for gap in source_acceptance_gaps if gap .get ("kind" ) == GOAL_ACCEPTANCE_STALE_TRIGGER ]
1625+ [gap for gap in source_acceptance_gaps if gap .get ("kind" ) in GOAL_ACCEPTANCE_HOLD_TRIGGERS ]
16851626 if vision_wait_state else source_acceptance_gaps
16861627 )
16871628 declared_fallback_gaps = [
0 commit comments