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 @@ -1034,9 +1034,11 @@ lineage. Ambiguous, stale, truncated or unrelated material changes cannot close
the current obligation. This closes one T3 rule group, not
the remaining consumers or T1/T2/D1–D3.

Long-chain scope correction (#4667): Agent lanes count 15 claimed advancement
Todos or 20 claimed open Todos with claimed advancement work. Shared candidates
remain selectable but no longer impose this duty; unscoped Goal observations
Long-chain scope corrections (#4667, #5001): Agent lanes require at least 15 claimed
open advancement Todos. Continuous monitors and shared candidates do not count;
shared candidates remain selectable. The former 20-claimed-open trigger is
retired for new Agent-lane obligations, while historical checkpoints and their
predecessor recovery remain readable. Unscoped Goal observations
retain the selectable-pool thresholds. Full material revisions include terminal advancement rows;
timestamp-only maintenance does not rearm them. A complete agent-owned identity
also keeps an accepted long-chain ACK valid when peers change shared unclaimed
Expand All @@ -1047,9 +1049,9 @@ an identity without a revision or an explicitly incomplete checkpoint cannot
suppress replanning. Other trigger kinds cannot borrow long-chain identity
matching. The same TS owner now supplies `obligation_identity_revision` from the
owned basis for the existing Python identity codec and predecessor proof: peer
churn cannot invalidate an open Turn before its ACK. Numeric thresholds and
write authority remain unchanged. `replan_semantics.ts` accepts and projects an
evidence-linked vision path for long-chain review, preserving existing progress
churn cannot invalidate an open Turn before its ACK. Monitor due selection,
no-change replan rules and write authority remain unchanged. `replan_semantics.ts`
accepts and projects an evidence-linked vision path for long-chain review, preserving existing progress
exits and stricter vision obligations. The real CLI regression follows the
projected binding through durable ACK, satisfied checkpoint, one spend and next
Turn readback; maintenance stays quiet and an owned material edit rearms.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -781,9 +781,10 @@ Python 验证前置 obligation id。压缩保留实质字段 `done`,历史保
多后继歧义、过期、来源截断或无关实质变化都不能关闭当前 obligation。
这闭合一个 T3 规则组,不代表其余 consumer 或 T1/T2/D1–D3 完成。

长链口径修正(#4667):Agent lane 统计 15 项已认领 advancement,或存在已认领
advancement 时的 20 项已认领 open Todo。共享候选仍可选,但不再计入本 lane 的
义务;无 Agent 的 Goal 总览保留原可选池口径。
长链口径修正(#4667、#5001):Agent lane 仅在已认领的开放 advancement 达到 15 项时触发。
持续监控和共享候选均不计入该阈值;共享候选仍可选。新义务不再使用原 20 项已认领
open Todo 的触发分支,历史 checkpoint 的读取与前置义务恢复保持兼容;无 Agent 的
Goal 总览保留原可选池口径。
完整实质 revision 包含终态 advancement;仅更新时间不重新触发。完整的 Agent-owned
identity 还能在同伴改变共享 unclaimed 工作时保持既有 long-chain ACK 有效。
自己的实质工作变化仍重新触发;没有认领工作的 lane 不产生长链义务。
Expand All @@ -792,7 +793,7 @@ owned identity;只有 identity 而没有 revision、或明确不完整的 chec
压制 replan;其他 trigger kind 不能借用长链身份匹配。同一 TS owner 现在提供
基于 owned 实质内容的 `obligation_identity_revision`,供既有 Python 身份 codec
及 predecessor 校验使用;同伴修改共享池不能在 ACK 前让本 Turn 的义务换 ID。
阈值数值和写权限不变。`replan_semantics.ts` 为长链 review 接受并投影带证据的
监控到期选择、无变化重规划规则和写权限不变。`replan_semantics.ts` 为长链 review 接受并投影带证据的
vision path,保留既有 progress 出口和严格 vision 义务。真实 CLI 回归沿投影绑定
验证持久 ACK、checkpoint、一次 spend 和下一 Turn 回读;维护不触发,自己任务的
实质修改重新触发。本次推进总路线 S2/S3 已有 T3 owner,不新增 provider、迁移存储
Expand Down
12 changes: 7 additions & 5 deletions docs/product/core-control-plane/state-machine.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,14 +475,16 @@ as a goal-frontier `acceptance_gaps[]` entry. If no advancement frontier remains
the gap becomes a replan trigger before the lane can quietly back off.

Long runnable lanes also pass through this machine. When the current agent owns
15 open advancement todos, or 20 claimed open todos with claimed advancement work
still present, quota should trigger a bounded vision replan before continuing
linearly. The replan reads the agent-scoped evidence log, uses bounded public
at least 15 open advancement todos, quota should trigger a bounded vision replan
before continuing linearly. The replan reads the agent-scoped evidence log, uses bounded public
research when local evidence is insufficient for a public claim, then groups,
prunes, or reprioritizes the chain into the next high-value runnable slice.
Shared unclaimed candidates remain selectable but do not count toward this lane
threshold. A valid evidence-linked vision path can retain existing runnable work
and settle the projected Turn without adding another planning Todo. Shared-pool
threshold. Continuous monitors also do not count: their due schedules and
no-change review rules remain independent, and the former 20-claimed-open
threshold no longer creates new Agent-lane obligations. Historical checkpoints
remain readable. A valid evidence-linked vision path can retain existing runnable
work and settle the projected Turn without adding another planning Todo. Shared-pool
churn preserves its obligation identity; owned material changes rearm it.

The same ordering also applies to `vision_checkpoint_v0`: if a role records
Expand Down
25 changes: 14 additions & 11 deletions docs/reference/protocols/goal-vision-replan-contract-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,8 +601,7 @@ or agent-scope wait decisions:
- normalized progress shows no remaining advancement frontier;
- monitor-only lanes have no material transition and acceptance remains open;
- a cleared handoff has no successor or no-follow-up rationale;
- the current agent lane owns at least 15 open advancement Todos, or 20 claimed
open Todos with claimed advancement work still present;
- the current agent lane owns at least 15 open advancement Todos;
- a periodic autonomous replan obligation is due;
- the user objective or acceptance contract changed;
- an approved dreaming proposal requires a delivery route.
Expand All @@ -611,19 +610,23 @@ The replan decision must not be disturbed by monitor quiet skip, scoped gate
waiting, or a single agent having no runnable todo. Those may explain local
lane state, but they cannot erase a required goal-level replan.

Long-chain scope correction (#4667): Agent-scoped counts now exclude shared
unclaimed candidates. They remain selectable, but do not create a replan duty
for a lane that has not claimed them. Unscoped Goal observations retain the
selectable-pool thresholds. Numeric thresholds and other replan sources are
unchanged. The typed frontier owner supplies `obligation_identity_revision`
from the owned material identity, keeping an open obligation stable across
Long-chain scope corrections (#4667, #5001): Agent-scoped counts exclude shared
unclaimed candidates and continuous monitors. Shared candidates remain selectable,
but a new long-chain duty requires at least 15 claimed advancement Todos. The former
20-claimed-open threshold no longer triggers an Agent lane. Unscoped Goal
observations retain the selectable-pool thresholds; monitor due selection and
no-change replan rules are unchanged. The typed frontier owner supplies
`obligation_identity_revision` from the owned material identity, keeping an open obligation stable across
peer/shared-pool churn; `frontier_revision` retains the full selectable-source
checkpoint for diagnostics and historical ACK matching. Owned material changes
still rearm. Timestamp/evidence bookkeeping does not. Existing accepted ACKs
remain readable; an outstanding pre-upgrade Turn should refresh its guard.
remain readable, including predecessor recovery for historical open-count
obligations; an outstanding pre-upgrade Turn should refresh its guard.

长链触发范围修正:Agent lane 只统计自己已认领的任务;共享未认领任务仍可选取,
但不计入本 lane 的长链阈值。无 Agent 的 Goal 总览保留原可选池口径。
长链触发范围修正:Agent lane 只在自己已认领的开放推进任务达到 15 项时触发;
持续监控和共享未认领任务不计入该阈值,移除原 20 项已认领开放任务的触发分支。
共享任务仍可选取;无 Agent 的 Goal 总览保留原可选池口径,监控到期和无变化重规划
规则不变。历史开放任务计数 checkpoint 的读取与前置义务恢复保持兼容。
义务身份使用 typed owner 给出的 owned 实质 revision,同伴修改共享池不会让正在
处理的义务换 ID;自己任务的实质修改仍重新触发。证据补充或更新时间不重新触发。

Expand Down
12 changes: 7 additions & 5 deletions loopx/control_plane/todos/frontier_revision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type Row = {
type LongChainObservation = {
trigger_count: number;
count_kind: "selectable_advancement_todos" | "selectable_open_todos" |
"claimed_advancement_todos" | "claimed_open_todos";
"claimed_advancement_todos";
selectable_open_count: number; selectable_advancement_count: number;
current_agent_claimed_open_count: number;
current_agent_claimed_advancement_count: number; unclaimed_advancement_count: number;
Expand Down Expand Up @@ -168,6 +168,8 @@ function successorCheckpoints(request: JsonObject, agent: string | null): JsonOb
: trigger.current_agent_claimed_advancement_count) - 1;
const priorOpen = count(agent === null ? trigger.selectable_open_count
: trigger.current_agent_claimed_open_count) - 1;
// Retain the former open-count threshold when reading historical obligations.
// New Agent-lane observations below count only advancement commitments.
if (bindings.length === 0 && candidates.length === 1 && triggers.length === 1 &&
trigger.kind === TRIGGER && trigger.frontier_revision === source.frontier_revision && (priorAdvancement >= 15 || priorOpen >= 20 && priorAdvancement > 0)) {
const completeSource = indexed === null ? source : checkpoint(rows, agent);
Expand Down Expand Up @@ -244,15 +246,15 @@ export function evaluateLongTodoChain(value: unknown): JsonObject {
// A lane replans commitments it owns. Shared candidates remain selectable,
// but must not impose a chain obligation with no owned ACK fence.
const measuredAdvancement = agent === null ? advancement : current;
const measuredOpen = agent === null ? open : claimedOpen;
const threshold = measuredAdvancement >= 15 ? 15 : measuredOpen >= 20 && measuredAdvancement > 0 ? 20 : null;
const threshold = measuredAdvancement >= 15 ? 15
: agent === null && open >= 20 && measuredAdvancement > 0 ? 20 : null;
if (threshold === null) return {observation: null, decision: null};
const revision = readIndex(summary.advancement_frontier_revision_index, agent)
?? checkpoint(decodeRows(request.rows), agent);
const observation: LongChainObservation = {trigger_count: threshold === 15 ? measuredAdvancement : measuredOpen,
const observation: LongChainObservation = {trigger_count: threshold === 15 ? measuredAdvancement : open,
count_kind: agent === null
? threshold === 15 ? "selectable_advancement_todos" : "selectable_open_todos"
: threshold === 15 ? "claimed_advancement_todos" : "claimed_open_todos",
: "claimed_advancement_todos",
selectable_open_count: open, selectable_advancement_count: advancement,
current_agent_claimed_open_count: claimedOpen,
current_agent_claimed_advancement_count: current, unclaimed_advancement_count: unclaimed,
Expand Down
23 changes: 18 additions & 5 deletions tests/control_plane/test_effect_turn_live_quota_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -1160,11 +1160,17 @@ def test_retained_selection_reentry_stays_packet_free_and_signed(
assert envelope["writeback"]["spend_after_validation"] is False


@pytest.mark.parametrize(
("advancement_count", "requires_replan"),
[(1, False), (14, False), (15, True)],
)
def test_retained_selection_reentry_refreshes_provider_todos_before_replan(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
advancement_count: int,
requires_replan: bool,
) -> None:
"""A retained choice must see the same fresh frontier that deferred it."""
"""Refresh retained choices before applying the claimed advancement threshold."""

agent_id = "agent-provider-frontier"
selected_todo_id = "todo_provider_selected"
Expand Down Expand Up @@ -1199,12 +1205,14 @@ def test_retained_selection_reentry_refreshes_provider_todos_before_replan(
"text": (
"[P1] Continue the selected delivery."
if index == 0
else f"[P1] Preserve blocker context {index}."
else f"[P1] Provider frontier item {index}."
),
"role": "agent",
"status": "open",
"priority": "P1",
"task_class": "advancement_task" if index == 0 else "blocker",
"task_class": (
"advancement_task" if index < advancement_count else "blocker"
),
"claimed_by": agent_id,
"updated_at": f"2026-09-{(index % 9) + 1:02d}T00:00:00Z",
}
Expand Down Expand Up @@ -1247,9 +1255,14 @@ def fresh_todos(**_kwargs: object) -> dict[str, object]:
)

assert reads == ["provider"]
assert payload["decision"] == "autonomous_replan_required"
assert payload["selected_todo"]["todo_id"] == selected_todo_id
assert payload["retained_action_selection"]["disposition"] == (
"preserve_retained_todo"
)
assert payload["replan_action_packet"]["obligation_id"]
assert payload["interaction_contract"]["agent_channel"]["must_attempt"] is True
if requires_replan:
assert payload["decision"] == "autonomous_replan_required"
assert payload["replan_action_packet"]["obligation_id"]
else:
assert payload["decision"] == "run"
assert not payload.get("replan_action_packet")
55 changes: 55 additions & 0 deletions tests/control_plane/test_long_chain_projected_closeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,68 @@
import json
from pathlib import Path

import pytest

from tests.control_plane.test_quota_settlement_cli import (
AGENT_ID, GOAL_ID, SELECTED_REPLAN_TODO_ID, TURN_ID,
_configure_selected_todo_replan_fixture, _projected_cli_args,
_run_cli, _spend_run_count, _write_fixture,
)


@pytest.mark.parametrize("advancement,monitors,replan_required", [
(9, 19, False), (14, 6, False), (15, 19, True),
])
def test_managed_turn_counts_advancement_without_monitor_backlog(
tmp_path: Path, advancement: int, monitors: int, replan_required: bool,
) -> None:
project, runtime, registry = _write_fixture(tmp_path)
_configure_selected_todo_replan_fixture(project, registry)
state = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md"
header = state.read_text(encoding="utf-8").split("## Agent Todo\n\n")[0]
rows = [
f"- [ ] [P1] Validate bounded slice {index}.\n"
f" <!-- loopx:todo todo_id=todo_chain_{index:012d} status=open "
"task_class=advancement_task action_kind=validate "
f"claimed_by={AGENT_ID} updated_at=2026-08-01T00%3A00%3A00Z -->\n"
for index in range(advancement)
]
rows.extend(
f"- [ ] [P2-monitor] Watch independent target {index}.\n"
f" <!-- loopx:todo todo_id=todo_monitor_{index:012d} status=open "
"task_class=continuous_monitor action_kind=observe "
f"claimed_by={AGENT_ID} target_key=synthetic-target-{index} "
"cadence=1h next_due_at=2099-01-01T00%3A00%3A00Z "
"updated_at=2026-08-01T00%3A00%3A00Z -->\n"
for index in range(monitors)
)
state.write_text(header + "## Agent Todo\n\n" + "".join(rows), encoding="utf-8")

# The public managed-Turn entrypoint must keep both the first read and replay
# free of a long-chain duty introduced solely by independent monitors.
for _ in range(2):
rc, result = _run_cli(
registry, runtime, "quota", "should-run", "--codex-app",
"--goal-id", GOAL_ID, "--agent-id", AGENT_ID,
"--turn-instance-id", TURN_ID, "--scan-path", str(project), cwd=project,
)
assert rc == 0, result
frontier = result["goal_frontier_projection"]
assert frontier["remaining_advancement_frontier"][
"current_agent_claimed_advancement_count"
] == advancement
assert frontier["replan_required"] is replan_required
assert result["selected_todo"]["todo_id"] == SELECTED_REPLAN_TODO_ID
obligation = result["heartbeat_recommendation"].get("replan_obligation")
if replan_required:
assert [trigger["kind"] for trigger in obligation["triggers"]] == ["long_todo_chain"]
assert obligation["triggers"][0]["count_kind"] == "claimed_advancement_todos"
assert obligation["triggers"][0]["trigger_count"] == advancement
else:
assert obligation is None
assert _spend_run_count(runtime) == 0


def test_projected_vision_replan_settles_without_a_meta_successor(tmp_path: Path) -> None:
project, runtime, registry = _write_fixture(tmp_path)
_configure_selected_todo_replan_fixture(project, registry)
Expand Down
Loading
Loading