Skip to content

Commit c8ef17c

Browse files
committed
Merge remote-tracking branch 'origin/main' into codex/canonical-todo-readmodel-0924
2 parents 313bf4a + e07ee86 commit c8ef17c

12 files changed

Lines changed: 273 additions & 128 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,14 @@ These priorities do not change live Goal quota or authorize experiments/cloud re
378378
- **Exit:** independent readback proves retained commitments; distinguish all-gap/partial/stale/rejected/committed; recovery neither duplicates nor expands work, and the initiating surface displays the exact outcome. Add independent semantic counterexamples, not only row-existence assertions.
379379
- **Rollback:** stop new plan production, keep old previews/receipts readable and unfinished reconciliation available; do not delete materialized work.
380380

381+
**Recovery checkpoint (2026-09-24):** Enabled acceptance now routes missing as
382+
well as stale associations through bounded agent-scoped replan. Exact hold
383+
checkpoints survive crowded vision projections; a replan decision does not
384+
simultaneously select a candidate monitor. Owner binding and fresh completion
385+
validation remain required. This repairs R1/S2/S3 continuity, not R4's general
386+
intent-preserving amendment or provider promotion. Real File/SQLite CLI
387+
regressions cover recovery admission and continued refusal of unbound completion.
388+
381389
### R2: Continuous Small-team Execution
382390

383391
**Product responsibility.** The steward owns the owner's cross-project context,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,12 @@ R2 的一条依赖必须通过真实 LoopX Agent 间的请求/产物交接完成
336336
- **退出:** 独立回读证明承诺保留;all-gap/partial/stale/rejected/committed 区分;中断后恢复不复制、不扩大工作,原接收界面显示精确结果。新增独立语义反例,不能只断言一行存在。
337337
- **回滚:** 停新计划 producer,保留可读旧预览/receipt 及未完成对账;不删除已经形成的工作。
338338

339+
**恢复检查点(2026-09-24):** 已启用验收的缺失关联与过期关联统一进入 Agent 范围的
340+
有界重规划;精确阻塞检查点不会被拥挤的 vision 展示挤掉,重规划不再同时选择候选观察任务。
341+
所有者绑定与完成时的新鲜验证仍然必需。这修复 R1/S2/S3 的持续运行路径,不代表 R4 通用
342+
意图保持 amendment 或 provider 晋升。真实 File/SQLite CLI 回归覆盖恢复准入与继续拒绝
343+
未绑定任务完成。
344+
339345
### R2:小团队持续执行
340346

341347
**产品职责。** 管家负责所有者跨项目的上下文、取舍和注意力;项目 coordinator 是

‎docs/reference/goal-acceptance-observations.md‎

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,28 @@ acceptance criteria remain separately configured and checked. Text, whether
156156
completion validation is required, repository/write-scope declarations, and
157157
unknown future work fields still invalidate the association. Replacing an
158158
existing `resume_when` is not reconstructible from the latest Todo and remains
159-
`stale`. When the agent's applicable Todo is genuinely stale, the existing
160-
Goal frontier projects its exact Todo ID as an agent-scoped replan trigger if
161-
no advancement Todo is selectable. The agent inspects the binding and work
162-
delta, restores an unintended edit or records an evidence-linked path change.
163-
The original Turn/Todo identity remains intact; a successor has its own identity.
164-
The agent cannot rebind
165-
owner-confirmed criteria, complete held work, or settle a different Todo under
166-
the old Turn; a true change to owner-owned criteria or scope still needs owner
167-
review. Only an evidence-linked runnable successor or concrete blocker receipt
168-
for this stale-binding trigger, recorded after the Todo update, quiets repeated
169-
wakeups until another material change. Existing enabled contracts configured
159+
`stale`. When no advancement Todo is selectable, both missing (`unbound`)
160+
and stale associations enter the existing agent-scoped recovery lane. Recovery
161+
preserves the original Turn/Todo identity and does not authorize execution of
162+
held work. Inspect a missing association and prepare it for owner confirmation;
163+
for a stale association, inspect the work delta and restore an unintended edit
164+
or propose the changed association. An already eligible successor remains a
165+
separate execution identity. Do not create another unbound repair Todo and
166+
mistake its existence for a runnable successor.
167+
168+
Acceptance holds precede general vision gaps in the bounded trigger packet, so
169+
the Agent can read the exact checkpoints that recovery requires. Only an
170+
accepted, evidence-linked runnable successor or concrete blocker receipt covering
171+
the exact hold generation can quiet it. Unrelated progress, a vision rewrite,
172+
or an acknowledgement for another association cannot discharge the hold.
173+
Reconfirming the contract or changing the work rearms recovery. Disabled/absent
174+
acceptance retains its existing behavior. When quota selects replan, candidate
175+
monitors remain inventory; `selected_todo` and `agent_lane_next_action` do not
176+
advertise them as this decision's execution target. Original receipt identities
177+
remain in their settlement contracts. A genuinely Todo-bound replan still
178+
projects its selected Todo; this only removes the unrelated inventory fallback.
179+
180+
Existing enabled contracts configured
170181
with a persisted `no_followup` field under the earlier digest rule require owner inspection and
171182
reconfiguration; no historical receipt is rewritten or automatically accepted.
172183
Disabled/absent acceptance retains its existing behavior.
@@ -316,12 +327,18 @@ claim、lease/fence、权限和后续工作要求。既有验证回执或已确
316327
重复确认,也不改写已保存的绑定摘要。修订后的命令仍须在完成时重新验证,Goal 验收条件
317328
也仍独立执行。任务文本、是否要求完成验证、仓库与写入范围等实质工作声明变化仍使关联
318329
过期;未知的新工作字段默认按实质变化处理。已有 `resume_when` 被替换时,当前 Todo
319-
无法证明旧值,仍保持 `stale`。适用的 Agent Todo 确实过期、且无可选推进任务时,现有
320-
Goal frontier 以原 Todo ID 产生 Agent 范围的重规划触发。Agent 核查关联与工作变化,
321-
恢复误改或记录有依据的路径变化。原 Turn/Todo 身份保持不变,后继有独立身份;Agent 不能自行重绑所有者确认
322-
的条件、完成受阻任务,或用原 Turn 结算另一条 Todo。真正改变所有者验收条件或范围的
323-
情况仍交所有者审阅。只有绑定该过期触发项、发生于 Todo 更新之后,并证明有依据的可运行
324-
后继或具体阻塞的回执,才会消解重复唤醒,直到再次出现实质变化。
330+
无法证明旧值,仍保持 `stale`。无可选推进任务时,缺失关联(`unbound`)与过期关联
331+
(`stale`)统一进入现有 Agent 范围的恢复路径。缺失关联需要准备关联方案供所有者确认;
332+
过期关联需要核查工作变化、恢复误改或提交变更后的关联。恢复保留原 Turn/Todo 身份,
333+
不授权执行或完成受阻任务,也不自动重绑。不要再新建一个同样 unbound 的修复 Todo,
334+
然后将其当作可运行后继。
335+
336+
有界触发项优先保留验收阻塞及其精确检查点,再展示通用 vision 缺口。只有覆盖该阻塞
337+
代次、被接纳的有据可运行后继或具体阻塞回执,才会消解重复唤醒。无关进展、改写 vision、
338+
其他关联的确认均不能代替;重新确认合同或改变工作会重新触发恢复。未启用时保持原行为。
339+
配额选择重规划时,候选观察任务仍可见于清单,但不再同时成为 `selected_todo` 或
340+
`agent_lane_next_action`;原回执身份仍由结算合同保留。真正绑定 Todo 的重规划仍展示原 Todo,
341+
本次仅移除从无关观察清单补出的选择。
325342

326343
`loopx goal-acceptance verify --goal-id example-goal` 仅预览;加 `--execute` 执行全部配置条件,
327344
再运行 inspect 读回。进入 **概览 → 交付与依据**,刷新并展开交付链下方的 **Goal 验收合同**。

‎loopx/control_plane/goals/goal_frontier/__init__.py‎

Lines changed: 33 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from __future__ import annotations
22

3-
import hashlib
4-
import json
53
from typing import Any
64

75
from ...agents.agent_scope import (
@@ -57,6 +55,10 @@
5755
)
5856
from ..goal_vision_wait import build_goal_vision_wait_state
5957
from . import outcome_continuity
58+
from .acceptance import (
59+
GOAL_ACCEPTANCE_HOLD_TRIGGERS,
60+
acceptance_gaps_from_held_goal_binding,
61+
)
6062
from .ack_policy import (
6163
autonomous_replan_ack_satisfies_obligation,
6264
replan_successor_transition_ack,
@@ -106,7 +108,6 @@
106108
FRONTIER_EXHAUSTED_MONITOR_TRIGGER = "frontier_exhausted_monitor_lane"
107109
MONITOR_NO_CHANGE_STREAK_TRIGGER = "monitor_no_change_streak"
108110
VISION_PROFILE_MISSING_TRIGGER = "required_agent_vision_missing"
109-
GOAL_ACCEPTANCE_STALE_TRIGGER = "goal_acceptance_stale"
110111
TODO_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

435373
def 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

Comments
 (0)