Skip to content
Closed
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 @@ -349,12 +349,25 @@ Todo readers retain their existing authority and scope. A selected-work
acceptance document cannot replace whole-Goal intent. Shared host adapters
preserve selected context. Default heartbeat generation → guard → source
readback must be tested alongside envelope transport; one route cannot qualify
the other. This does not
implement the full requirement ledger, a global closeout gate, automatic proof
of required-read execution, or model adherence. Those remain with R1/R4/R5 and
S11; this RFC does not silently enable a new acceptance policy. The next complete
slice should map existing structured criteria through a work switch and restart,
reject dropped coverage, and invalidate stale evidence under the same owner.
the other.

For an enabled Goal-wide acceptance contract, the existing TS owner now retains
a canonical verification gap when active advancement work is exhausted but full
current verification has not passed. The gap keeps every configured criterion
ID and its current contract/work basis. Quota selects replan; TurnEnvelope retains
the contract and replan packet. A Todo-bound Host still blocks an empty selection;
the gap grants no new execution authority. Neither a vision ACK nor local `no_followup` can clear it or trigger
terminal automation shutdown. Full current verification clears this necessary
condition. Freshness uses the existing contract/work bindings, without a new
artifact freshness oracle. Contract revisions invalidate old success; historical receipts remain
unchanged. Explicit `selected_work` contracts keep their local scope, and
disabled/absent governance keeps its previous behavior.

This does not implement the full requirement ledger, a complete Goal-lifecycle
closeout gate, automatic proof of required-read execution, or model adherence.
Those remain with R1/R4/R5 and S11. Coverage is limited to the owner's declared
contract; it does not prove that all natural-language requirements were captured
or that a model follows them over a long run.

### 3.8 Invalid evidence and affected consumers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,20 @@ canonical acceptance 与当前 Todo 精确详情读取。普通产品 heartbeat
消费同一清单;短包只传输并签名,不另行制定读取规则。Goal 文档恢复完整意图和停止
条件,canonical acceptance 与 Todo reader 保留各自权威和范围;selected-work 验收
文档不能替代完整目标。共享 host adapter 保留选中任务。须一起验证默认 heartbeat
生成→guard→来源回读与短包传输,不能用一条路径替另一条验收。尚未
实现完整义务账本、全局收口门禁、读取执行的自动证明或模型遵循资格。这些继续归
R1/R4/R5 与 S11,本 RFC 不隐式开启新的 acceptance policy。下一完整切片应让既有
结构化 criterion 穿过任务切换和重启,在同一 owner 拒绝覆盖丢失并使过期证据失效。
生成→guard→来源回读与短包传输,不能用一条路径替另一条验收。

已启用 Goal 全局验收合同时,若当前推进工作已耗尽、但当前完整合同验证尚未通过,
既有 TS owner 现在保留 canonical verification gap。缺口保留全部已配置 criterion ID
及当前合同/工作基线。quota 选择重规划,TurnEnvelope 保留合同与重规划包。
依赖 Todo 的 Host 仍阻止空任务选择;缺口不授予新执行权威。vision ACK 或局部
`no_followup` 均不能消除此缺口,也不能据此永久停止自动化。当前完整验证通过后,
这项必要条件才解除。时效性沿用既有合同/工作绑定,不新增产物时效判定器。
合同修订使旧成功过期,历史回执保持原样。显式 `selected_work`
合同保持局部范围,停用或未配置验收时保持原行为。

这尚未实现完整义务账本、完整 Goal 生命周期收口门禁、读取执行的自动证明或模型
遵循资格。这些继续归 R1/R4/R5 与 S11。覆盖范围仅限所有者声明的合同,不能证明
自然语言要求已被完整提取,也不能证明模型在长期运行中始终遵循它们。

### 3.8 失效证据与受影响消费者

Expand Down
24 changes: 24 additions & 0 deletions docs/reference/goal-acceptance-observations.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,30 @@ The section exists only when the server's
contracts retain the baseline UI and export. The existing snapshot export and
status Markdown include the enabled readback without command bodies or raw logs.

When an enabled Goal-wide contract has no active advancement work left, an
`unverified`, `partial`, `failed` or `stale` result remains a verification gap.
Quota and Turn planning retain all configured criterion IDs and require replan;
local Todo completion, `no_followup` and a vision ACK cannot replace the missing
verification or derive `terminal_no_followup`. Inspect the contract and run the
checks above from its delivery workspace. Repair unmet work or record a concrete
blocker when necessary. Full current verification clears the gap; a later
contract or work revision can reopen it. Reading or polling never runs checks
automatically. `selected_work` contracts keep their local coverage, and absent
or disabled governance retains its previous behavior. This is a necessary
configured-contract check, not proof of whole-Goal success or model adherence.
The gap fingerprint binds Goal/instance, contract/work basis and verification
facts. Repeating identical results under a new operation ID is not new progress.

已启用 Goal 全局验收合同、且当前推进工作耗尽时,未验证、局部通过、失败或过期结果
仍保留验收缺口。quota 与 Turn planning 保留全部 criterion ID 并要求重规划;局部
Todo 完成、`no_followup` 或 vision ACK 不能代替验证,也不能推出 `terminal_no_followup`。
应从交付工作区 inspect 并运行上面的检查;必要时修复未满足的工作或记录具体阻塞。
当前完整验证通过后才解除缺口,合同或工作修订可使缺口重开。读取或轮询不会自动执行
检查。`selected_work` 仍只管局部工作,停用/未配置时保持原行为。这项必要检查不证明
整个 Goal 已成功,也不证明模型长期不偏离目标。
缺口指纹绑定 Goal/实例、合同/工作基线与验证事实;仅更换 operation ID 重复相同
结果不算新进展。

| Server state | Readback meaning |
| --- | --- |
| Task `ready` | Owner confirmed the current task association; artifact checks are separate |
Expand Down
21 changes: 21 additions & 0 deletions loopx/control_plane/goals/acceptance_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,10 +482,31 @@ export function projectGoalAcceptance(head: JsonObject, goalId: string): JsonObj
else status = "accepted";
}
if (held.length) status = "held";
// Empty work is not evidence for the remaining Goal-wide criteria. A local
// contract still governs only its declared work, never unrelated closeout.
const verificationGap = state.document.scope?.kind !== "selected_work" && status !== "accepted" &&
![...todos.values()].some(acceptanceApplies) ? {
kind: "vision_acceptance_gap", source: "goal_acceptance_contract",
canonical_verification_required: true,
criterion_ids: state.document.criteria.map(criterion => criterion.id),
frontier_revision: canonicalAuthoritySha256({goal_id: goalId,
goal_ref: authority.kind === "exact" ? authority.lifecycle.goal_ref : null,
revision: state.revision, digest: state.digest,
work_digest: goalAcceptanceWorkDigest(head, goalId, state.document.scope), status,
verification: receipt === null ? null : {contract_revision: receipt.contract_revision,
contract_digest: receipt.contract_digest, work_digest: receipt.work_digest,
todo_id: receipt.todo_id, results: receipt.results}}),
replan_trigger_summary: `Goal-wide acceptance remains ${status} after the advancement frontier closed.`,
acceptance_summary: "Retain every declared Goal criterion until current full-contract verification passes.",
resolution_hint: "Inspect the current Goal acceptance contract and run its configured checks with " +
"goal-acceptance verify from the delivery workspace. Repair unmet work or record a concrete blocker " +
"when needed. A vision or no-followup note cannot replace canonical verification.",
} : null;
return {...exactProjection, ...(state.document.scope === undefined ? {} : {scope: state.document.scope}),
enabled: true, revision: state.revision, digest: state.digest, objective: state.document.objective,
non_goals: state.document.non_goals, criteria: state.document.criteria.map(({id, description}) => ({id, description})),
tasks, held_todo_ids: held.map(task => task.todo_id), status,
...(verificationGap === null ? {} : {verification_gap: verificationGap}),
verification: receipt ? {operation_id: receipt.operation_id, contract_revision: receipt.contract_revision,
contract_digest: receipt.contract_digest, todo_id: receipt.todo_id, results: receipt.results} : null};
}
Expand Down
9 changes: 8 additions & 1 deletion loopx/control_plane/goals/goal_frontier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,9 @@ def derive_goal_frontier_replan_obligation_from_summaries(
},
)
if replan_rule.rule is GoalFrontierReplanRule.VISION_ACCEPTANCE_GAP:
verification_required = any(
gap.get("canonical_verification_required") is True for gap in compact_acceptance_gaps
)
acceptance_held = any(
gap.get("kind") in GOAL_ACCEPTANCE_HOLD_TRIGGERS for gap in compact_acceptance_gaps
)
Expand Down Expand Up @@ -1226,6 +1229,8 @@ def derive_goal_frontier_replan_obligation_from_summaries(
"completed_todo_ids",
"vision_todo_ids",
"frontier_revision",
"criterion_ids",
"canonical_verification_required",
"reason_code",
"component_checks",
"resolution_hint",
Expand All @@ -1236,13 +1241,15 @@ def derive_goal_frontier_replan_obligation_from_summaries(
for gap in compact_acceptance_gaps[:3]
],
guidance_actions=(
["use_replan_context", "create_successor", "record_evidence_gap"]
if verification_required else
["inspect_acceptance_binding", "select_eligible_successor", "record_evidence_gap"]
if acceptance_held else
["create_successor", "update_agent_vision", "record_evidence_gap", "record_no_followup"]
),
# Another unbound advancement Todo cannot repair this admission
# hold. Keep authoring suggestions aligned with the typed exits.
todo_actions=[] if acceptance_held else [
todo_actions=[] if acceptance_held or verification_required else [
{
"action": "add",
"role": "agent",
Expand Down
10 changes: 7 additions & 3 deletions loopx/control_plane/goals/goal_frontier/acceptance.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,30 @@ def acceptance_gaps_from_held_goal_binding(
*,
agent_id: str | None,
) -> list[dict[str, Any]]:
"""Route missing or stale acceptance through this agent's replan lane.
"""Retain native verification gaps and route scoped association holds.

This is a read-only trigger, not an acceptance rebind. Other runnable work
remains selectable; the frontier rule schedules a replan only when no
advancement Todo can be selected for this agent.
"""

if not agent_id or not isinstance(agent_todo_summary, dict):
if not isinstance(agent_todo_summary, dict):
return []
contract = agent_todo_summary.get("goal_acceptance_contract")
if not isinstance(contract, dict) or contract.get("enabled") is not True:
return []
verification_gap = contract.get("verification_gap")
gaps = ([{**verification_gap, "agent_id": agent_id}]
if isinstance(verification_gap, dict) else [])
if not agent_id:
return gaps
held_states = {
task.get("todo_id"): task.get("state")
for task in contract.get("tasks", [])
if isinstance(task, dict)
and task.get("state") in {"stale", "unbound"}
and task.get("applicable") is True
}
gaps: list[dict[str, Any]] = []
for item in source_items or []:
if (
not isinstance(item, dict)
Expand Down
10 changes: 9 additions & 1 deletion loopx/control_plane/goals/goal_frontier/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,17 @@ def _terminal_todo_source_contract(
"user_todos": user_status,
"agent_todos": agent_status,
}
contract = (
agent_todo_summary.get("goal_acceptance_contract")
if isinstance(agent_todo_summary, dict) else None
)
verification_gap = (
contract.get("verification_gap") if isinstance(contract, dict) else None
)
return (
source_completeness,
user_status == agent_status == "valid" and (user_intent or agent_intent),
user_status == agent_status == "valid" and (user_intent or agent_intent)
and not isinstance(verification_gap, dict),
)


Expand Down
5 changes: 4 additions & 1 deletion loopx/control_plane/work_items/replan_semantics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,10 @@ function acknowledgeVisionEvidence(request: JsonObject): JsonObject {
return timestamp(gap.generated_at ?? (gap.kind === "vision_outcome_checkpoint_required" &&
gap.source === "recent_completed_advancement_todo" ? gap.completed_at : null));
});
return {acknowledged: ack.recorded === true && delta.accepted === true &&
// Only the acceptance owner can clear its current verification gap.
const verificationRequired = request.acceptance_gaps.some(value =>
object(value).canonical_verification_required === true);
return {acknowledged: !verificationRequired && ack.recorded === true && delta.accepted === true &&
strings(delta.outcomes).some(outcome => VISION_OUTCOMES.some(known => known === outcome)) &&
acknowledgedAt !== null &&
gaps.length > 0 && gaps.every(time => time !== null && time <= acknowledgedAt)};
Expand Down
Loading
Loading