Skip to content

Commit 0d3cc51

Browse files
authored
Merge pull request #4122 from huangruiteng/codex/canonical-quota-consumers
refactor(todos): retire Python resume planning rules into TypeScript
2 parents 3e10aff + 51abdfd commit 0d3cc51

22 files changed

Lines changed: 1083 additions & 834 deletions

‎docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2544,6 +2544,12 @@ The next complete stage packages are:
25442544
monitor/resume effects close together. Neither an admission result nor a
25452545
lease-fence result is a commit receipt. Keep provider CAS/replay and existing
25462546
writer lock lifetimes unchanged while collecting this deletion payoff.
2547+
Waiting/resume lane selection is now one TS read-policy owner shared by quota,
2548+
vision-wait, agent-scope and replan. The obsolete Python selector module is
2549+
deleted; the adapter accepts the same canonical summary after promotion and
2550+
legacy summary before it. Real CLI coverage includes capacity changes and
2551+
missing promoted display without writing it. This does not close all quota
2552+
source paths, authorize monitor writeback, or change provider/promotion holds.
25472553
2. **Permanent projection closure.** Reuse `provider_projection.py`, the
25482554
Todo-section renderer and existing journal/outbox. Preserve non-owned human
25492555
narrative; render owned sections from a known canonical revision, with

‎docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,6 +2015,11 @@ backend、实时双向同步或按命令拆开的权威;晋升后不支持的
20152015
这不是完整 native 字段编辑:在 update 的字段、ownership、validation 和 monitor/resume
20162016
effect 一起闭合前,保留严格 text/note 事务边界。准入结果和 lease-fence 结果都不是
20172017
commit receipt;兑现删除收益时,provider CAS/replay 与既有 writer 持锁生命周期不变。
2018+
等待/恢复 lane 选择现由 quota、vision-wait、agent-scope、replan 共用一个 TS 读取
2019+
策略 owner,删除旧 Python selector 模块。适配层在 promotion 后消费同一 canonical
2020+
summary,之前消费 legacy summary;真实 CLI 覆盖容量变化和 promoted display
2021+
缺失且不写回的场景。这不代表所有 quota source 路径已闭合,不授予 monitor 写回
2022+
权限,也不改变 provider 默认与 promotion hold。
20182023
2. **永久投影闭合。** 复用 `provider_projection.py`、Todo-section renderer 和既有
20192024
journal/outbox。保留非托管的人工叙述,从已知 canonical revision 渲染托管 section,
20202025
提供幂等修复与 freshness/readback 证据。投影 pending 独立于业务 commit/replay。

‎docs/architecture/rfcs/typescript-control-plane-migration-v0.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,40 @@ this slice reduces semantic owners, not crossing count. Native transactions stay
244244
in-process. Fold the remaining crossings into that complete transaction rather
245245
than extending these adapters field by field.
246246

247+
The waiting/resume planning slice now uses `todos/resume_planning.ts` for the
248+
complete deferred, resume-blocked, monitor-repair and blocked-successor selection.
249+
Quota composes capacity evaluation with these lanes in one request per source summary, reusing the
250+
existing TS resume evaluator in-process; vision-wait, agent-scope, frontier and
251+
replan consumers use the same projection. The old `deferred_resume.py` rule owner
252+
is removed, not retained behind a second implementation. The Python adapter keeps
253+
the reader compatibility boundary, not claim/exclusion selection or wait routing.
254+
Resume, route-continuation and succession-warning share `compact_projection.py`
255+
for field omission and scope normalization; caller-specific text inference and
256+
succession-only fields remain explicit. Priority rank normalization stays in the
257+
resume adapter. This retires
258+
one read-policy family, not the whole quota reducer or the monitor/lease writers.
259+
Equal public sort keys retain source order; full counts precede display limits;
260+
`monitor_changed` is not the legacy `todo_done:<monitor>` repair path. This
261+
read-only result grants neither execution authority nor a lifecycle receipt.
262+
The adapter exits when its callers consume typed Todo records in-process.
263+
264+
Resume condition diagnosis is now shared by the evaluator and planning owner;
265+
agent-scope consumes the selected repair lane rather than reinterpreting target
266+
type/status. Old compact inputs may recover omitted kind/class from typed
267+
`resume_when` and the same snapshot's monitor records, never from narrative.
268+
This refinement includes explicit behavior corrections: self-dependencies and
269+
`todo_done` dependencies on unfinished monitors are `resume_condition_invalid`,
270+
not ordinary pending waits. Completed historical monitor dependencies remain
271+
satisfied; missing completion targets remain pending because absence in a
272+
partial snapshot is not proof of an invalid dependency. Valid generation fences,
273+
claim/exclusion, capacity and PR waits retain their existing semantics. Invalid
274+
conditions cannot become exact blocked-successor waits. Monitor completion
275+
repair stays visible and selectable only in the permitted executor scope.
276+
No automatic conversion to `monitor_changed`, baseline reset, persisted-state
277+
rewrite or new writer admission is implied. General add/update admission and a
278+
generic repair action for every invalid condition remain separate scopes; this
279+
is not a claim of zero behavior change or full Todo writer closure.
280+
247281
1. **Close the actual command and consumer inventory.** Build on the merged
248282
create/claim/update and #4053 terminal/successor/archive transactions; do not
249283
recreate them. Inventory remaining field-edit, monitor, lease, and event

‎docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,30 @@ legacy update writer**。字段 patch、省略/清空、monitor/resume effect
191191
语义 owner,不宣称减少 crossings,native transaction 仍进程内调用。下一步将这些
192192
crossing 一起折叠进完整事务,不能沿着 adapter 逐字段继续加桥。
193193

194+
等待/恢复规划现由 `todos/resume_planning.ts` 一次完成 deferred、resume-blocked、
195+
monitor-repair 和 blocked-successor 选择。Quota 为每个 source summary 将容量条件与这些 lane 合为一个请求,
196+
在 TS 进程内复用既有 resume evaluator;vision-wait、agent-scope、frontier、replan
197+
共用此投影。删除旧 `deferred_resume.py` 规则 owner,不保留第二份实现。Python 适配层
198+
只保留 reader 兼容边界,不再决定 claim/exclusion 选择或等待路由。Resume、
199+
route-continuation、succession-warning 共用 `compact_projection.py` 的字段省略与 scope
200+
归一化;各 caller 的文本推断差异及 succession 独有字段显式保留,priority rank
201+
归一化仍在 resume adapter。这闭合一个读取策略族,不是整个 quota reducer,也未
202+
迁移 monitor/lease writer。相同公开排序键保持 source 顺序;完整计数先于展示截断;
203+
`monitor_changed` 不进入旧 `todo_done:<monitor>` 修复路径。只读结果不授予执行权限,
204+
也不是生命周期 receipt;caller 在进程内消费 typed Todo record 后可删除此适配层。
205+
206+
条件 evaluator 与规划 owner 现在共用恢复条件诊断;agent-scope 消费已选好的修复 lane,
207+
不再重新解释 target 类型/状态。旧 compact 输入缺少 kind/class 时,只从 typed
208+
`resume_when` 和同一快照的 Monitor 记录补足,不从叙述猜测。本次 refinement 包含
209+
明确行为修正:自依赖,以及对未完成 Monitor 的 `todo_done` 依赖,被诊断为
210+
`resume_condition_invalid`,不再当作普通 pending wait。历史已完成 Monitor 依赖仍可
211+
满足;完成依赖的目标缺失仍为 pending,因局部快照中的缺失不能证明依赖非法。合法的
212+
generation fence、claim/exclusion、capacity 和 PR 等待语义保持。非法条件不进入
213+
精确 blocked-successor 等待;Monitor 完成依赖的修复仍可见,且仅在合法执行者范围内
214+
可选。此诊断不自动改写为 `monitor_changed`、重置 baseline、重写持久状态或增加写入
215+
准入。普通 add/update 准入及覆盖全部非法条件的通用修复动作仍是独立范围;不能宣称
216+
全量零行为变化或全部 Todo writer 已闭合。
217+
194218
1. **闭合实际命令与 consumer 清单。** 基于已合入的 create/claim/update 和 #4053
195219
terminal/successor/archive transaction 推进,不重复建设。按真实合同盘点剩余
196220
字段编辑、monitor、lease、event caller,把规则迁入既有 TS owner,并在同一切片

‎examples/control_plane/todo-deferred-resume-lanes-smoke.py‎

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,7 @@
1111
if str(REPO_ROOT) not in sys.path:
1212
sys.path.insert(0, str(REPO_ROOT))
1313

14-
from loopx.control_plane.todos.deferred_resume import ( # noqa: E402
15-
TODO_DEFERRED_RESUME_SELECTION_POLICY,
16-
TODO_MONITOR_BLOCKED_RESUME_SELECTION_POLICY,
17-
build_todo_deferred_visibility_lanes,
18-
build_todo_resume_blocked_visibility_lanes,
19-
todo_summary_monitor_blocked_resume_items,
20-
todo_summary_resume_blocked_items,
21-
)
14+
from loopx.control_plane.todos.resume_planning import project_todo_resume_planning # noqa: E402
2215

2316

2417
CURRENT_AGENT = "codex-product-capability"
@@ -116,11 +109,11 @@ def assert_deferred_resume_lanes_filter_current_unclaimed_and_other_agents() ->
116109
],
117110
}
118111

119-
lanes = build_todo_deferred_visibility_lanes(
112+
lanes = project_todo_resume_planning(
120113
summary,
121-
agent_identity={"agent_id": CURRENT_AGENT},
114+
agent_id=CURRENT_AGENT,
122115
item_limit=10,
123-
)
116+
)["deferred_lanes"]
124117
assert lanes["deferred_count"] == 1, lanes
125118
assert lanes["deferred_visibility_limit"] == 10, lanes
126119
assert lanes["deferred_items"][0]["todo_id"] == "todo_deferred_backlog", lanes
@@ -140,9 +133,6 @@ def assert_deferred_resume_lanes_filter_current_unclaimed_and_other_agents() ->
140133
current = lanes["current_agent_deferred_resume_candidates"][0]
141134
assert current["required_write_scopes"] == ["loopx/**"], current
142135
assert current["decision_scope"]["scope_key"] == "resume", current
143-
assert lanes["deferred_resume_selection_policy"] == (
144-
TODO_DEFERRED_RESUME_SELECTION_POLICY
145-
), lanes
146136

147137

148138
def assert_monitor_blocked_resume_lanes_filter_by_claim_and_monitor_target() -> None:
@@ -181,15 +171,17 @@ def assert_monitor_blocked_resume_lanes_filter_by_claim_and_monitor_target() ->
181171
"backlog_items": [current],
182172
}
183173

184-
resume_blocked = todo_summary_resume_blocked_items(summary)
174+
projection = project_todo_resume_planning(summary, agent_id=CURRENT_AGENT, item_limit=10)
175+
lanes = projection["resume_blocked_lanes"]
176+
resume_blocked = lanes["resume_blocked_items"]
185177
assert [item["todo_id"] for item in resume_blocked] == [
186178
"todo_current_blocked",
187179
"todo_unclaimed_blocked",
188180
"todo_other_blocked",
189181
"todo_non_monitor_blocked",
190182
"todo_excluded_blocked",
191183
], resume_blocked
192-
monitor_blocked = todo_summary_monitor_blocked_resume_items(summary)
184+
monitor_blocked = projection["monitor_blocked_items"]
193185
assert [item["todo_id"] for item in monitor_blocked] == [
194186
"todo_current_blocked",
195187
"todo_unclaimed_blocked",
@@ -201,20 +193,12 @@ def assert_monitor_blocked_resume_lanes_filter_by_claim_and_monitor_target() ->
201193
for item in monitor_blocked
202194
), monitor_blocked
203195

204-
lanes = build_todo_resume_blocked_visibility_lanes(
205-
summary,
206-
agent_identity={"agent_id": CURRENT_AGENT},
207-
item_limit=10,
208-
)
209196
assert lanes["resume_blocked_count"] == 5, lanes
210197
assert lanes["monitor_blocked_resume_count"] == 4, lanes
211198
assert lanes["current_agent_monitor_blocked_resume_count"] == 1, lanes
212199
assert lanes["unclaimed_monitor_blocked_resume_count"] == 1, lanes
213200
assert lanes["other_agent_monitor_blocked_resume_count"] == 1, lanes
214201
assert lanes["executor_excluded_self_monitor_blocked_resume_count"] == 1, lanes
215-
assert lanes["monitor_blocked_resume_selection_policy"] == (
216-
TODO_MONITOR_BLOCKED_RESUME_SELECTION_POLICY
217-
), lanes
218202

219203

220204
def main() -> int:

‎loopx/control_plane/agents/agent_scope.py‎

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
work_lane_contract_requires_current_agent_attempt,
1818
)
1919
from ..todos.contract import (
20-
TODO_STATUS_OPEN,
2120
TODO_TASK_CLASS_ADVANCEMENT,
2221
TODO_TASK_CLASS_MONITOR,
2322
normalize_todo_blocks_agent,
@@ -26,11 +25,9 @@
2625
normalize_todo_excluded_agents,
2726
normalize_todo_global_gate,
2827
normalize_todo_id,
29-
normalize_todo_status,
30-
normalize_todo_task_class,
3128
)
3229
from ..todos.handoff_gate import HandoffGateState
33-
from ..todos.deferred_resume import todo_summary_blocked_successor_items
30+
from ..todos.resume_planning import project_todo_resume_planning
3431
from ..todos.projection import (
3532
todo_item_claimed_by_agent_or_unclaimed,
3633
todo_item_excludes_agent,
@@ -936,28 +933,16 @@ def _agent_scope_monitor_blocked_resume_candidates(
936933
continue
937934
if item.get("resume_ready") is not False:
938935
continue
939-
raw_condition = item.get("resume_condition")
940-
condition = raw_condition if isinstance(raw_condition, dict) else {}
941-
if normalize_todo_status(condition.get("target_status")) != TODO_STATUS_OPEN:
942-
continue
943-
target_todo_id = normalize_todo_id(
944-
item.get("blocking_monitor_todo_id")
945-
or condition.get("target_todo_id")
946-
or condition.get("target")
947-
)
948-
target_task_class = normalize_todo_task_class(
949-
condition.get("target_task_class"),
950-
text="",
951-
)
952-
if target_task_class != TODO_TASK_CLASS_MONITOR and not target_todo_id:
953-
continue
936+
# The typed resume-planning owner has already diagnosed and selected
937+
# this repair lane. This consumer keeps executor scope and presentation,
938+
# not a second interpretation of condition target/class/status.
954939
identity = str(item.get("todo_id") or item.get("index") or item.get("text") or "")
955940
if identity in seen:
956941
continue
957942
seen.add(identity)
958943
compact = compact_todo_summary_item(item, text=str(item.get("text") or "").strip())
959-
if target_todo_id:
960-
compact["blocking_monitor_todo_id"] = target_todo_id
944+
if item.get("blocking_monitor_todo_id"):
945+
compact["blocking_monitor_todo_id"] = item["blocking_monitor_todo_id"]
961946
unique.append(compact)
962947
return sorted(unique, key=_todo_projection_sort_key)
963948

@@ -1261,10 +1246,10 @@ def _deferred_resume_frontier(
12611246
def _blocked_successor_wait_frontier(
12621247
context: _AgentScopeNoCandidateContext,
12631248
) -> dict[str, Any] | None:
1264-
candidates = todo_summary_blocked_successor_items(
1249+
candidates = project_todo_resume_planning(
12651250
context.summary,
12661251
agent_id=context.agent_id,
1267-
)
1252+
)["blocked_successor_items"]
12681253
if not candidates:
12691254
return None
12701255
first = candidates[0]

‎loopx/control_plane/effect_runtime_handlers.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import {
7171
planTodoExternalWaitTransition,
7272
} from "./todos/resume_condition.ts";
7373
import { evaluateSchedulerStateTransition } from "./scheduler/state_transition_rules.ts";
74+
import { projectTodoResumePlanning } from "./todos/resume_planning.ts";
7475
import {
7576
evaluateSchedulerStateOperation,
7677
loadSchedulerState,
@@ -394,6 +395,7 @@ export function createEffectRuntimeHandlers(
394395
["todo.next_action.transition", transitionTodoNextAction],
395396
["todo.resume_condition.normalize", normalizeTodoResumeWhen],
396397
["todo.resume_condition.evaluate", evaluateTodoResumeConditions],
398+
["todo.resume_planning.project", projectTodoResumePlanning],
397399
["todo.external_wait.plan", planTodoExternalWaitTransition],
398400
["scheduler.state_transition.evaluate", evaluateSchedulerStateTransition],
399401
["scheduler.state.evaluate", evaluateSchedulerStateOperation],

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from ...todos.contract import (
77
normalize_todo_id,
88
)
9-
from ...todos.deferred_resume import todo_summary_blocked_successor_items
9+
from ...todos.resume_planning import project_todo_resume_planning
1010
from ...todos.projection import (
1111
agent_scoped_selectable_advancement_todo_ids,
1212
)
@@ -125,10 +125,10 @@ def _blocked_successor_todo_ids(
125125
todo_id
126126
for todo_id in (
127127
normalize_todo_id(item.get("todo_id"))
128-
for item in todo_summary_blocked_successor_items(
128+
for item in project_todo_resume_planning(
129129
agent_todo_summary,
130130
agent_id=agent_id,
131-
)
131+
)["blocked_successor_items"]
132132
if isinstance(item, dict)
133133
)
134134
if todo_id
@@ -148,10 +148,10 @@ def _blocked_primary_waiting(
148148
if isinstance(blocker_items, list) and blocker_items:
149149
return True
150150
return bool(
151-
todo_summary_blocked_successor_items(
151+
project_todo_resume_planning(
152152
agent_todo_summary,
153153
agent_id=agent_id,
154-
)
154+
)["blocked_successor_items"]
155155
)
156156

157157

‎loopx/control_plane/goals/goal_vision_wait.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
normalize_todo_id,
1313
normalize_todo_status,
1414
)
15-
from ..todos.deferred_resume import todo_summary_blocked_successor_items
15+
from ..todos.resume_planning import project_todo_resume_planning
1616

1717
GOAL_VISION_WAIT_STATE_SCHEMA_VERSION = "goal_vision_wait_state_v0"
1818
VISION_ACCEPTANCE_GAP_KIND = "vision_acceptance_gap"
@@ -170,9 +170,9 @@ def _covered_wait_items(
170170
and normalize_todo_claimed_by(item.get("claimed_by")) == safe_agent_id
171171
and not todo_item_excludes_agent(item, agent_id=safe_agent_id)
172172
]
173-
candidates = todo_summary_blocked_successor_items(
173+
candidates = project_todo_resume_planning(
174174
agent_todo_summary or {}, agent_id=agent_id
175-
)
175+
)["blocked_successor_items"]
176176
coverage = effect_runtime_result(
177177
"goal.vision_wait.coverage",
178178
{

0 commit comments

Comments
 (0)