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
@@ -0,0 +1,41 @@
# Selected Todo source read in the existing context transaction

The accepted request is to retire Python bridge work without creating parallel semantic owners. This slice moves the complete selected-Todo source read and its source-fact construction into the existing `work_item.context.project` request. Goal document, User-scope and acceptance/oracle IO remain Python providers.

| Boundary | Result |
| --- | --- |
| Before | Python reads the complete canonical collection, selects one active Todo, adds its content revision, then sends it to the TS context projector. |
| After | `readAndProjectInteractionWorkContext` uses the existing canonical snapshot reader in-process, then invokes the unchanged context validator/projector. |
| Retirement | The Python selected-Todo branch and source-reader imports are removed. The containing adapter remains for its real Goal/acceptance IO callers. |
| Integrity | The same existing-only store route, complete snapshot validation, page/revision fencing, archived/missing refusal, raw body, normalized content revision, status and claim checks remain. There is no Markdown fallback. |
| Transport | No RPC method is added. The existing context projection receives `source_context`; the canonical read retains its existing 15-second budget. The existing context plan/project phases remain; the remaining Python providers keep their own calls. The selected-Todo snapshot no longer crosses separately. |
| Feature off | A request without a selected-Todo required read does not open a provider. Prepared-source native callers retain the existing pure projection. |
| Recovery | A changed selected body fails the public CLI on both File and SQLite. Bypassing the typed source guard makes both independent regression cases fail. |
| Authority | This read grants no claim, lease, execution, acceptance or write authority. No persisted format or live Goal data is migrated. |

## Evidence and limits

The immutable initial baseline is `ecbf905402ccfd9e4d75b2b963b52376b4ef7332`; later-main integration and final exact-head evidence belong in the PR. Existing failures in four selected-work tests reproduce on that baseline and concern the prior capture/output layout. Their repair belongs to existing PR #7430; this slice does not relax those assertions.

Durable checks live in `tests/control_plane_ts/interaction_context_readback.test.ts` and `tests/control_plane/test_selected_work_requirements.py`. They cover complete requirements/notes, source revision, changed/archived/missing/corrupt/unavailable state, no provider creation, feature-off behavior and unchanged canonical data. Wheel and sdist execution, matched full-CLI costs and actual source revision are recorded separately in the PR; test counts or fewer requests do not prove whole-Goal outcome value.

The shared normalized-text revision helper serves the existing canonical display codec and the new source read. No policy is reimplemented in Python. Retire the remaining Python context adapter only when its document, User-scope and oracle providers have real native callers; removing a file based on its language is not the exit criterion.

## Final source proposal qualification

Integration base: `d29ec8bf2a105aac24629bcbf3dfb7c97aea51fe`. Source candidate: `c137daecc56a5f1784097742b212d26b27f83892` (subsequent documentation updates do not change the four production files).

- Python adapter: +7/-35 lines, net -28. Total production diff: +63/-37, net +26. This is source/transport consolidation, not a claim that previously Python-owned domain policy moved.
- Source checks: 138 focused/related Python tests and 93 native tests pass; four older capture-layout failures remain baseline evidence. Mutation of the source guard breaks both real File/SQLite body-change regressions.
- Fresh wheel and sdist: File and SQLite command readbacks pass in four isolated installation cases, with source-file hashes checked.
- The actual `loopx` entrypoint on SQLite, with 500 historical records and 30 alternating matched pairs, has p50 647→657 ms and p95 779→799 ms. The default entrypoint stays within the declared 5%/25 ms regression bounds; no speedup is claimed.
- The compatibility `python -m loopx.cli` entrypoint has paired p95 1309→1486 ms. This remains an unresolved performance hold. The observed imports/parser hotspots do not prove the tail's cause; raw results and earlier samples are retained. Complete cost/Host adoption qualification is not certified by this proposal.
- Complete inline work context matches in all 30 default-entry pairs. Event IDs are identical with only the event-clock input fixed. Differences in collection clocks and their computed ages remain in raw observations; no ownership, body, field, refusal or identity is normalized.

**Author implementation is complete; merge qualification remains held for performance review and a fresh exact-head review.** No merge, deployment, authority migration or persistent-state deletion is performed.

### Later upstream capture integration

Candidate `ff4e94459` integrates latest base `ce4cfc1ce`. Its four changed production files remain byte-identical to `c137daecc`. Upstream now returns capture navigation; the existing product reader verifies and opens the complete current projection. Actual File/SQLite entrypoint readbacks on this base preserve complete work context and event identity; logical Effect calls are 74→73 in these newer observations.

Six historical direct-stdout assertions fail on both latest main and candidate because they still expect an inline `should_run` field. Both selected runs have 2 pass /6 fail, with the same identities and failure boundary. Those failures remain evidence. Earlier installed/cost measurements retain their stated revision; latest-main full cost and Host qualification remain held.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 当前 Todo 的来源读取并入现有上下文事务

本轮要求是退役 Python 桥接工作,同时保持单一规则所有者。本切片把当前 Todo 的完整来源读取和来源字段组装并入现有 `work_item.context.project` 请求。Goal 文档、User 作用域和 acceptance/oracle 的 IO 继续由 Python provider 执行。

| 边界 | 结果 |
| --- | --- |
| 迁移前 | Python 读取完整 canonical 集合,选出一个 active Todo,补充正文版本,然后发给 TS 上下文投影器。 |
| 迁移后 | `readAndProjectInteractionWorkContext` 在进程内复用现有 canonical 快照读取器,再调用原有上下文校验与投影。 |
| 退役 | 删除 Python 的 selected-Todo 分支和来源读取 import。包含它的适配器仍有真实 Goal/acceptance IO 调用方,因此保留。 |
| 完整性 | 保留同一个只读既有 store 路由、完整快照校验、分页与版本约束、归档/缺失拒绝、原始正文、规范化正文版本、状态与认领检查。不增加 Markdown fallback。 |
| 传输 | 不新增 RPC。现有上下文投影接收 `source_context`;canonical 读取沿用原有 15 秒预算。原有上下文 plan/project 阶段保留;其余 Python provider 仍有各自的调用。当前 Todo 快照不再单独跨运行时传输。 |
| 关闭路径 | 没有 selected-Todo required read 时,不打开 provider。已提供来源的原生调用方继续使用原有纯投影。 |
| 恢复 | 正文在选择后改变时,File/SQLite 的公开 CLI 均拒绝交付。绕过 typed 来源门禁后,两条独立回归测试均失败。 |
| 权限 | 本次读取不授予认领、lease、执行、验收或写入权限。不迁移持久格式或活跃 Goal 数据。 |

## 证据与范围

初始不可变基线为 `ecbf905402ccfd9e4d75b2b963b52376b4ef7332`。更新主干后的集成和最终准确 head 证据写入 PR。四项 selected-work 测试在该基线也失败,原因是旧 capture/输出布局断言。已有 PR #7430 负责修复,本切片不放宽这些断言。

保留的检查位于 `tests/control_plane_ts/interaction_context_readback.test.ts` 和 `tests/control_plane/test_selected_work_requirements.py`。检查覆盖完整正文与独立备注、正文版本、变更/归档/缺失/损坏/不可用来源、不创建 provider、关闭路径和 canonical 数据不变。wheel/sdist 的实际执行、完整 CLI 匹配成本与实际源码版本在 PR 单列;测试数量和更少请求不能证明整个 Goal 的成果价值。

共享的规范化正文版本函数同时服务原有 canonical 展示 codec 与新的来源读取。Python 不重建规则所有者。只有文档、User 作用域和 oracle provider 都有真实原生调用方后,才退役其余 Python 上下文适配器;语言本身不是删除条件。

## 最终源码候选资格

集成基线:`d29ec8bf2a105aac24629bcbf3dfb7c97aea51fe`。源码候选:`c137daecc56a5f1784097742b212d26b27f83892`;后续文档更新不改变四个生产文件。

- Python 适配器新增7行、删除35行,净减28行。生产代码合计新增63行、删除37行,净增26行。本次收敛来源与传输工作,不声称迁走了原本由 Python 持有的领域规则。
- 138项聚焦/相关 Python 测试和93项原生测试通过。四项旧 capture 布局失败保留为基线证据。绕过来源门禁后,File/SQLite 两条真实正文变更回归均失败。
- wheel/sdist 在独立环境执行,File/SQLite 四个实际命令读回通过,并核对源码文件哈希。
- 实际 `loopx` 入口使用 SQLite、500条历史记录和30对交错匹配样本:p50 为647→657 ms,p95 为779→799 ms。默认入口在既有5%/25 ms回退范围内,不声称加速。
- 兼容的 `python -m loopx.cli` 入口,匹配样本 p95 为1309→1486 ms,仍属未闭合的性能阻断。已观察到的导入/参数注册热点不能证明尾延迟的根因;原始结果与较早样本全部保留。本候选不认证完整成本或真实 Host 采用。
- 默认入口30对样本的完整内联工作上下文一致。仅固定事件时钟输入后,事件ID一致。采集时钟和计算出的age差异保留在原始观察中,不归一化身份、归属、正文、字段或拒绝结果。

**作者实现已完成;性能复核和最终准确 head 的复审完成前,继续阻断合并。** 本轮不执行合并、部署、authority迁移或持久数据删除。

### 后续上游 capture 集成

候选 `ff4e94459` 集成最新基线 `ce4cfc1ce`,四个改动生产文件与 `c137daecc` 字节一致。上游现在返回 capture 导航引用;原有产品读取器校验后打开完整当前观察。在该基线上,实际 File/SQLite 入口的完整工作上下文与事件身份保持一致;业务 Effect 调用在这组较新观察中为74→73。

六项旧断言仍直接读取 stdout 顶层 `should_run`,因此最新 main 和候选均为2通过/6失败,失败身份与边界相同。失败继续保留。此前安装包和成本测量保留各自版本;不声称最新主干的完整成本或 Host 资格已通过。
Original file line number Diff line number Diff line change
Expand Up @@ -2846,3 +2846,4 @@ with shared write/read status-phase checks. See the [2026-10-02 checkpoint](ledg
- Complete adaptive child admission decision: [2026-11-02 checkpoint](ledger/typescript-control-plane-migration-v0/2026-11-02-adaptive-child-admission.md).
- Progress-review receipt qualification: [2026-11-02 checkpoint](ledger/typescript-control-plane-migration-v0/2026-11-02-progress-review-qualification.md).
- Global Todo readiness complete owner cutover (both consumers): [2026-11-02 ledger](ledger/typescript-control-plane-migration-v0/2026-11-02-global-todo-readiness.md).
- Selected Todo source read inside the context transaction: [2026-11-04](ledger/typescript-control-plane-migration-v0/2026-11-04-selected-context-source-read.md).
Original file line number Diff line number Diff line change
Expand Up @@ -2111,3 +2111,4 @@ SQLite 中位数略升,收益不能外推为全面加速。这只资格化本
- 完整自适应子 Agent 准入决策:[2026-11-02 检查点](ledger/typescript-control-plane-migration-v0/2026-11-02-adaptive-child-admission.zh-CN.md)。
- 进度评审 receipt 资格判定:[2026-11-02 检查点](ledger/typescript-control-plane-migration-v0/2026-11-02-progress-review-qualification.zh-CN.md)。
- Global Todo 就绪分类完整 owner 切换(两个消费者):[2026-11-02 记录](ledger/typescript-control-plane-migration-v0/2026-11-02-global-todo-readiness.zh-CN.md)。
- 当前 Todo 来源读取并入上下文事务:[2026-11-04](ledger/typescript-control-plane-migration-v0/2026-11-04-selected-context-source-read.zh-CN.md).
2 changes: 1 addition & 1 deletion loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,7 @@ export function createEffectRuntimeHandlers(
["work_item.interaction_reads.project", lazyHandler(() => import("./quota/turn_envelope.ts"), ({projectInteractionReadsAndGuidance}) => projectInteractionReadsAndGuidance)],
["work_item.blocked_wait_observation.project", lazyHandler(() => import("./work_items/interaction_contract.ts"), ({projectBlockedSuccessorWaitObservation}) => projectBlockedSuccessorWaitObservation)],
["work_item.context.plan", lazyHandler(() => import("./work_items/interaction_contract.ts"), ({planInteractionWorkContext}) => planInteractionWorkContext)],
["work_item.context.project", lazyHandler(() => import("./work_items/interaction_contract.ts"), ({projectInteractionWorkContext}) => projectInteractionWorkContext)],
["work_item.context.project", lazyHandler(() => import("./work_items/interaction_contract.ts"), ({readAndProjectInteractionWorkContext}) => readAndProjectInteractionWorkContext)],
["work_item.replan_settlement.project", lazyHandler(() => import("./work_items/replan_settlement.ts"), ({projectReplanSettlementContract}) => projectReplanSettlementContract)],
["work_item.replan_semantics.project", lazyHandler(() => import("./work_items/replan_semantics.ts"), ({projectReplanWriteback}) => projectReplanWriteback)],
["work_item.replan_context.project", lazyHandler(() => import("./work_items/replan_context.ts"), ({projectReplanContext}) => projectReplanContext)],
Expand Down
6 changes: 5 additions & 1 deletion loopx/control_plane/todos/read_codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ export function canonicalTodoReadItems(values: JsonObject[]): JsonObject[] {
});
}

export function normalizedTodoTextRevision(normalizedText: string): string | null {
return normalizedText ? "sha256:" + hash(normalizedText) : null;
}

export function structureCanonicalTodo(item: JsonObject, role: unknown, section: unknown,
limit: number | null = 500, revision = true): JsonObject {
const full = compactPythonWhitespace(text(item.text)), body = compactTodoReadText(full, limit);
Expand All @@ -205,7 +209,7 @@ export function structureCanonicalTodo(item: JsonObject, role: unknown, section:
return {...readMetadata(item), schema_version: "todo_item_v0", role, source_section: section,
archive_state: item.archive_state ?? "active", status, done: status === "done" || status === "deferred",
text: body, task_class: taskClass({...item, text: body}),
...(revision && full ? {content_revision: "sha256:" + hash(full)} : {}),
...(revision && full ? {content_revision: normalizedTodoTextRevision(full)} : {}),
...(parsed.priority ? {priority: parsed.priority, title: compactTodoReadText(parsed.title, limit)} : {})};
}

Expand Down
42 changes: 7 additions & 35 deletions loopx/control_plane/work_items/context_readback.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@

from ...history import load_registry
from ..effect_runtime import effect_runtime_result
from ..coordination.local_authority import read_canonical_todos_if_promoted
from ..coordination.local_authority import LOCAL_COORDINATION_TODO_LIST_TIMEOUT_SECONDS
from ..goals.acceptance import inspect_goal_acceptance
from ..goals.state_resolution import resolve_goal_state
from ..todos.list_readback import list_goal_todos
from ..todos.machine_region import find_todo_regions, visible_markdown_lines
from ..scheduler.monitor_batch_receipts import is_monitor_receipt_line
from ..todos.summary_item import todo_text_content_revision


def _source_content(read, *, registry_path, runtime_root, goal_id, todo_id, agent_id=None,
Expand Down Expand Up @@ -74,37 +73,6 @@ def _source_content(read, *, registry_path, runtime_root, goal_id, todo_id, agen
# through the bounded acceptance RPC before discarding it here.
return inspect_goal_acceptance(registry_path=registry_path, goal_id=goal_id,
runtime_root=str(runtime_root), todo_id=todo_id, context_agent_id=agent_id, include_work_details=False)
if source == "selected_todo":
if not todo_id:
raise ValueError("selected Todo identity is unavailable")
canonical = read_canonical_todos_if_promoted(
runtime_root=runtime_root, goal_id=goal_id)
records = canonical["todos"]
source_name = canonical["source_authority"]
authority_read = {
"source_authority": source_name,
"provider_revision": canonical.get("provider_revision"),
"decision_read_from_provider": True,
"legacy_fallback_used": False,
}
matches = [item for item in records
if item.get("todo_id") == todo_id
and item.get("archive_state", "active") != "archive"]
todo = dict(matches[0]) if len(matches) == 1 else None
if isinstance(todo, dict) and "status" not in todo:
todo["status"] = "done" if todo.get("done") is True else "open"
if isinstance(todo, dict) and isinstance(todo.get("text"), str):
revision = todo_text_content_revision(todo["text"])
if revision:
todo["content_revision"] = revision
return {
"ok": True,
"matched": len(matches) == 1,
"ambiguous": len(matches) > 1,
"todo": todo,
"source": source_name,
"authority_read": authority_read,
}
raise ValueError("unregistered work context source")


Expand Down Expand Up @@ -142,7 +110,7 @@ def attach_work_context(payload: dict[str, Any], *, registry_path: Path,
selected["_context_text_sha256"] = selected_text_snapshot
results = [dict(item) for item in (hook_dispatch or {}).get("contexts", [])]
for read in reads:
if read.get("source") not in {"goal_state", "goal_acceptance", "selected_todo"}:
if read.get("source") not in {"goal_state", "goal_acceptance"}:
continue
result = {"command": read["command"]}
try:
Expand All @@ -163,8 +131,12 @@ def attach_work_context(payload: dict[str, Any], *, registry_path: Path,
"required_reads": reads, "source_results": results,
"selected_todo": plan.get("selected_todo"), "user_todos": users,
"hook_dispatch": hook_dispatch,
"source_context": {"runtime_root": str(runtime_root.expanduser().resolve(strict=False)), "goal_id": goal_id},
**({"require_full_goal_source": True} if inline_goal_source else {}),
}, large_local_snapshot=True)
}, large_local_snapshot=True, **(
{"timeout": LOCAL_COORDINATION_TODO_LIST_TIMEOUT_SECONDS}
if any(read.get("source") == "selected_todo" for read in reads) else {}
))
# An older runtime must not silently ignore the requested delivery profile.
profile = projected.pop("source_delivery_profile", None)
if inline_goal_source and profile != "complete_goal_v1":
Expand Down
Loading
Loading