Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3211,6 +3211,15 @@ soak, release, merge and live promotion retain their respective authorization.
| I. Binding and qualification integration | After C and the selected profile's qualification | Bind one exact provider lineage, field manifest, source revision, digest, and cursor; qualify explicit v0 import, ordering/archival/consumer parity, and recovery/capacity without consulting legacy state for missing fields. | Long-goal local integration requires L and does not wait for P. PostgreSQL joins only when its own P holds pass. |
| F. Promotion and cleanup | After I and explicit maintainer approval | Complete provider-first CLI routing, the lock-owning promotion orchestrator, compatibility projection outbox, post-promotion fenced export/rollback, then delete duplicate reference aggregates and flip the reviewed stage/hold declarations. | Each profile must pass C, I, and its own provider qualification; long-goal local promotion additionally requires L, and PostgreSQL requires P. |

Agent-addressed read checkpoint: Todo list filtering now joins the typed summary
batch and shares User gate/action and Agent claim addressing with quota. The
Python list predicate is retired on both legacy and canonical consumers; full
source resume/succession and post-filter counts survive display limits. This is
one L5 consumer closure, not D1 projection freshness or provider promotion. See
[read semantics](../../reference/todo-work-counts.md). Remaining caller/executor,
consumer recovery, contributor D2, capture/whole-Goal and default onboarding
boundaries retain the conditional **5–8 cohesive PR** estimate.

## Appendix D: Execution ledger

Delivery records for this RFC are files under
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2523,6 +2523,13 @@ adapter,也不依赖 PostgreSQL service 部署。
| I. Binding 与资格集成 | C 与选定 profile 的资格化完成后 | 绑定一个精确 provider lineage、field manifest、source revision、digest 与 cursor;资格化显式 v0 import、排序/归档/consumer parity 与 recovery/capacity;缺字段时不得查询 legacy state 补齐。 | 长程本地集成需要 L,不等待 P;PostgreSQL 仅在自己的 P hold 全通过后汇合。 |
| F. Promotion 与清理 | I 完成且 maintainer 显式批准后 | 完成 provider-first CLI routing、持锁 promotion orchestrator、兼容投影 outbox、晋升后 fenced export/rollback;随后删除重复 reference aggregate,并翻转经评审的 stage/hold 声明。 | 每个 profile 必须通过 C、I 与自身 provider 资格化;长程本地晋升还需 L,PostgreSQL 还需 P。 |

Agent 定向读取检查点:Todo list 筛选已进入现有 TS summary 批次,与 quota 共用
User gate/action 及 Agent claim 范围规则;legacy 和 canonical 消费者中的 Python
列表谓词已删除。完整来源上的 resume/succession 与筛选后的计数不受展示上限影响。
这只闭合 L5 的一个消费者,不代表 D1 永久新鲜度或 provider 晋升。见[读取合同](../../reference/todo-work-counts.md)。
剩余 caller/executor、consumer recovery、contributor D2、capture/整 Goal 演练和默认
onboarding 仍按 **5–8 个完整 PR** 条件估计,不能按本次修复机械递减。

## 附录 D:执行账本

本 RFC 的交付记录是 [`ledger/shared-goal-authority-state-provider-v0/`](ledger/shared-goal-authority-state-provider-v0/) 下的文件,
Expand Down
14 changes: 14 additions & 0 deletions docs/architecture/rfcs/typescript-control-plane-migration-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -1752,3 +1752,17 @@ behavior.
Measured delivery records live in the [per-entry ledger](ledger/typescript-control-plane-migration-v0/).
Each entry names its delivered boundary and remaining acceptance gaps; the T1–T4
checkpoints above remain the current migration plan.

### T2 Agent-addressed read checkpoint

Todo list selection now composes with the existing typed summary-lanes batch.
The Python role/status/id/Agent predicates and independent User scope rule are
removed; legacy and promoted consumers share `todos/agent_scope.ts` with quota
and decision scope. Explicit gate scope retains precedence over execution claim,
while retained User claims now correctly restrict scoped list visibility.
Full-source resume/succession stays evaluated before selection; original array
ordinals survive filters and display limits. No extra selection runtime crossing,
new capability/provider, or Python storage migration is introduced. Python keeps
input normalization and rendering until their actual host consumers migrate.
See [the read contract](../../reference/todo-work-counts.md); broader L5/D1 and
local-default qualifications remain open.
13 changes: 13 additions & 0 deletions docs/development/testing-and-quality.md
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,19 @@ it does not grant execution quota, spending, or provider authority.
证据中同时保留原失败与新结果。纯预算调整不必捆绑无关清理。已冻结的实验或
promotion 阈值不能追溯放宽;新阈值属于新一轮验证,不能改写历史结论。

A base/head differential must remain able to measure a syntactically and
semantically valid base that already exceeds its own historical ceiling;
otherwise the gate deadlocks the repair before observing the candidate. The
base-only probe may skip absolute size assertions while retaining parse,
required-key, anchor, and semantic differential checks. The candidate always
runs the current absolute budgets. Measurement-only mode is never a candidate
override or merge bypass.

当 base 已超过自身历史上限但输出仍可解析且语义完整时,base/head differential 必须
仍能采集它;否则门禁会在观察修复候选之前形成死锁。仅 base 的 probe 可跳过绝对尺寸
断言,但必须保留解析、必需字段、锚点和语义差异检查;candidate 始终执行当前绝对
预算。measurement-only 不能用于 candidate,也不是合并旁路。

The PR-review packet's `semantic_alignment` rule consumes this evidence through
the existing `validation_matrix` and `observable_semantics` rows. It does not
add a separate budget receipt or approval gate. The result checker verifies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ projection. Candidate lists and peer action lists retain counts and point to
`--include-detail vision`; `--include-detail all` restores every supported
detail section.

When a replan action carries a complete `vision_authoring` schema, the default
`quota should-run` packet keeps its executable writeback summary (`required_fields`,
accepted path outcomes, and rule) and replaces only that nested schema with a
`vision_authoring_detail_ref`. `--include-detail vision` restores the schema.
`turn plan` is different: its TurnEnvelope preserves the complete schema because
the plan must be capable of authoring the exact input its validator accepts.
The crowded Turn budget therefore accounts for that fixed contract without
relaxing Todo-count growth or the small and multi-Agent ceilings.

## Qualification Contract

Deterministic tests own exact full-versus-compact parity, cold-path restoration,
Expand Down
35 changes: 35 additions & 0 deletions docs/reference/todo-work-counts.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,31 @@ retain their old undercount behavior; rollback does not require data migration.
T1/T2 caller closure, D1 projection recovery, D2 capacity/elapsed soak and D3
fenced whole-Goal cutover remain separate work.

## Agent-addressed reads

`todo list --agent-id` now composes selection in the same typed summary batch,
sharing scope rules with quota. For User gates, explicit `global_gate` wins,
then `blocks_agent`, then the retained `claimed_by` fallback. User actions use
`bound_agent` first and retained `claimed_by` second. Unscoped records remain
visible. Gate addressing is independent of executor exclusion: an Agent cannot
ignore an explicitly addressed human gate because another Agent owns it.
Agent work still filters by claim and exclusions. A visible row grants no
mutation or execution permission; quota retains its additional eligibility rules.

This intentionally removes other-Agent, claim-only User records from scoped
lists; the old Python list rule ignored their claim while quota honored it.
Unfiltered Goal views retain those records. There is no feature flag or provider
default change. Existing frontend/Lark manager views use the unfiltered Core
read and continue to show the whole Goal; no new configuration editor is needed.

Resume and succession are evaluated on the complete source before selection.
The typed batch filters rows without renumbering their original source indexes,
then builds lanes/counts, and only then applies display limits. Status/identity
filters do not recompute dependencies from their smaller view. The v1 internal
request composes this selection into the existing call; v0 unfiltered callers
retain their wire contract. Python decodes legacy input and renders results,
with no independent Agent-addressing rule.

## 中文说明

`work_counts` 由完整来源计算,随后才裁剪展示。Agent quota 先按原有归属、排除、
Expand All @@ -77,3 +102,13 @@ TS 统一批量 lane 分类与计数,Python 保留旧格式解码、时间适
这不改变 provider 默认值,不授予执行权限,不写回 Markdown 或 canonical 状态。
新增计数字段不进入持久化 Todo;回滚无需数据迁移。默认切换、存量迁移、D1–D3 和旧
Python writer 退出仍有各自的验收条件,不能按本 PR 合并数量推定完成。

Agent 定向列表现与 quota 共用 TS 范围规则:User gate 按 global_gate → blocks_agent →
旧 claimed_by 依次判定,User action 按 bound_agent → 旧 claimed_by 判定。无作用域的
旧记录仍可见;显式人类 gate 不会被执行者 claim/exclusion 消除。Agent 工作仍按
claim/exclusion 筛选,可见不代表获准执行。

这是有意纠正:旧列表忽略仅声明 claimed_by 的 User 记录,导致其他 Agent 的工作混入
当前列表。未筛选的整 Goal 视图仍显示这些记录。依赖和 succession 先在完整来源求值,
TS 再筛选并保留原数组位置,最后生成 lanes、计数和有界展示;筛选后的数组位置不是原
来源位置。无需新增 capability、配置、前端或 Lark 编辑入口,不增加一次筛选 RPC。
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ def _run_probe(
fixture_root: Path,
receipt_path: Path,
cwd: Path,
enforce_budget: bool,
) -> None:
env = os.environ.copy()
env["PYTHONPATH"] = str(source_root)
_run(
[
command = [
sys.executable,
str(probe_runner),
"--test-source",
Expand All @@ -73,7 +73,11 @@ def _run_probe(
str(fixture_root),
"--receipt",
str(receipt_path),
],
]
if not enforce_budget:
command.append("--measurement-only")
_run(
command,
cwd=cwd,
env=env,
)
Expand Down Expand Up @@ -171,6 +175,7 @@ def main() -> int:
fixture_root=fixture_root,
receipt_path=base_receipt,
cwd=temp_root,
enforce_budget=False,
)
_run_probe(
source_root=REPO_ROOT,
Expand All @@ -180,6 +185,7 @@ def main() -> int:
fixture_root=fixture_root,
receipt_path=candidate_receipt,
cwd=temp_root,
enforce_budget=True,
)
finally:
_run(
Expand Down
135 changes: 108 additions & 27 deletions examples/control_plane/cli-output-probe-runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,36 @@ def _receipt_row(
}


def _assert_output_contract(
*,
output_format: str,
text: str,
measurement: dict,
semantic_json_keys: tuple[str, ...],
markdown_anchor: str | None,
) -> None:
"""Validate shape while allowing a red base to remain measurable."""

if output_format == "markdown":
if markdown_anchor and markdown_anchor not in text:
raise AssertionError(
f"markdown output lost semantic anchor {markdown_anchor!r}"
)
return
payload = measurement.get("payload")
if not isinstance(payload, dict):
raise AssertionError("JSON output did not emit an object")
missing = [key for key in semantic_json_keys if key not in payload]
if missing:
raise AssertionError(f"JSON output lost semantic key(s): {', '.join(missing)}")


def _default_rows(
probe: ModuleType,
semantics: ModuleType,
fixture_root: Path,
*,
enforce_budget: bool = True,
) -> list[dict]:
rows: list[dict] = []
for scenario in probe.SCENARIOS:
Expand All @@ -151,13 +177,22 @@ def _default_rows(
text, output_format=output_format
)
surface = probe.CLI_OUTPUT_BUDGET_BY_ID[surface_id]
probe.assert_cli_output_baseline(
surface,
scenario=scenario.name,
output_format=output_format,
text=text,
measurement=measurement,
)
if enforce_budget:
probe.assert_cli_output_baseline(
surface,
scenario=scenario.name,
output_format=output_format,
text=text,
measurement=measurement,
)
else:
_assert_output_contract(
output_format=output_format,
text=text,
measurement=measurement,
semantic_json_keys=surface.semantic_json_keys,
markdown_anchor=surface.markdown_anchor,
)
rows.append(
_receipt_row(
semantics=semantics,
Expand All @@ -184,6 +219,8 @@ def _variant_rows(
probe: ModuleType,
semantics: ModuleType,
fixture_root: Path,
*,
enforce_budget: bool = True,
) -> list[dict]:
project, runtime, registry_path, state_file = probe._write_fixture(
fixture_root / "mode_variants",
Expand All @@ -210,12 +247,21 @@ def _variant_rows(
if exit_code != 0:
raise AssertionError(f"{variant_id}/{output_format} failed")
measurement = probe.measure_cli_output(text, output_format=output_format)
probe.assert_cli_output_mode_variant(
variant,
output_format=output_format,
text=text,
measurement=measurement,
)
if enforce_budget:
probe.assert_cli_output_mode_variant(
variant,
output_format=output_format,
text=text,
measurement=measurement,
)
else:
_assert_output_contract(
output_format=output_format,
text=text,
measurement=measurement,
semantic_json_keys=variant.semantic_json_keys,
markdown_anchor=variant.markdown_anchor,
)
rows.append(
_receipt_row(
semantics=semantics,
Expand All @@ -238,6 +284,8 @@ def _blocking_gate_rows(
probe: ModuleType,
semantics: ModuleType,
fixture_root: Path,
*,
enforce_budget: bool = True,
) -> list[dict]:
project, runtime, registry_path, state_file = probe._write_fixture(
fixture_root / "blocking_user_gate",
Expand Down Expand Up @@ -266,12 +314,21 @@ def _blocking_gate_rows(
variant = probe.CLI_OUTPUT_MODE_VARIANT_BY_ID[
"quota_should_run_turn_envelope"
]
probe.assert_cli_output_mode_variant(
variant,
output_format="json",
text=output,
measurement=measurement,
)
if enforce_budget:
probe.assert_cli_output_mode_variant(
variant,
output_format="json",
text=output,
measurement=measurement,
)
else:
_assert_output_contract(
output_format="json",
text=output,
measurement=measurement,
semantic_json_keys=variant.semantic_json_keys,
markdown_anchor=variant.markdown_anchor,
)
return [
_receipt_row(
semantics=semantics,
Expand All @@ -293,7 +350,7 @@ def _blocking_gate_rows(



def _multi_subagent_rows(probe, semantics, fixture_root):
def _multi_subagent_rows(probe, semantics, fixture_root, *, enforce_budget=True):
"""Run the same enabled public fixture on base and head, not default-off only."""
project, runtime, registry_path, state_file = probe._write_fixture(
fixture_root / "multi_subagent_enabled", probe.SCENARIOS[0]
Expand All @@ -314,9 +371,16 @@ def _multi_subagent_rows(probe, semantics, fixture_root):
raise AssertionError("enabled multi_subagent turn envelope failed")
measurement = probe.measure_cli_output(output, output_format="json")
variant = probe.CLI_OUTPUT_MODE_VARIANT_BY_ID[variant_id]
probe.assert_cli_output_mode_variant(
variant, output_format="json", text=output, measurement=measurement,
)
if enforce_budget:
probe.assert_cli_output_mode_variant(
variant, output_format="json", text=output, measurement=measurement,
)
else:
_assert_output_contract(
output_format="json", text=output, measurement=measurement,
semantic_json_keys=variant.semantic_json_keys,
markdown_anchor=variant.markdown_anchor,
)
return [_receipt_row(
semantics=semantics,
row_id="variant/quota_should_run_turn_envelope_multi_subagent/small/json",
Expand All @@ -334,6 +398,11 @@ def main() -> int:
parser.add_argument("--semantics-source", type=Path, required=True)
parser.add_argument("--fixture-root", type=Path, required=True)
parser.add_argument("--receipt", type=Path, required=True)
parser.add_argument(
"--measurement-only",
action="store_true",
help="measure a historical base without requiring its retired ceilings to pass",
)
args = parser.parse_args()
_install_pytest_import_stub()
probe = _load_module("loopx_cli_output_probe_fixture", args.test_source)
Expand All @@ -343,10 +412,22 @@ def main() -> int:
args.fixture_root.mkdir(parents=True)
with probe._stable_budget_fixture_root(args.fixture_root) as stable_root:
rows = [
*_default_rows(probe, semantics, stable_root),
*_variant_rows(probe, semantics, stable_root),
*_blocking_gate_rows(probe, semantics, stable_root),
*_multi_subagent_rows(probe, semantics, stable_root),
*_default_rows(
probe, semantics, stable_root,
enforce_budget=not args.measurement_only,
),
*_variant_rows(
probe, semantics, stable_root,
enforce_budget=not args.measurement_only,
),
*_blocking_gate_rows(
probe, semantics, stable_root,
enforce_budget=not args.measurement_only,
),
*_multi_subagent_rows(
probe, semantics, stable_root,
enforce_budget=not args.measurement_only,
),
]
args.receipt.parent.mkdir(parents=True, exist_ok=True)
args.receipt.write_text(
Expand Down
Loading
Loading