Skip to content
Open
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
38 changes: 38 additions & 0 deletions docs/architecture/rfcs/typescript-control-plane-migration-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,44 @@ Each page reuses the existing executable-revision request scope; operation
records remain live reads, and the next page resolves the current code again.


### Todo read transaction source checkpoint

`todo.read` now composes canonical provider reads, full-source resume and
succession evidence, standing decisions, both role summaries, selection and the
public result through `todos/read_projection.ts` and `todos/read_transaction.ts`.
Ordinary canonical queries use one request. Large result continuations retain
one observation and result digest; changed results refuse rather than combining
pages. Dependency and closure decisions still use the complete source.
Python retains registry, document/material IO, independent Vision-wait consumers
and compatibility exports. Its replaced summary, filter and result coordination
is retired. Persisted completion spellings use shared token data and compatibility codecs
without scalar RPCs;
`todo.summary.project`, `todo.completion_state.normalize` and
`todo.completion_state.require_metadata` are retired after their production
callers exit. Existing semantic owners execute in process inside the transaction.

CLI query results carry a query-scoped `projection_envelope`. A read grants no
execution, acceptance, lease or mutation authority. The shared wire and private
snapshot budgets are unchanged. Oversized Python facts use an owned temporary
source, not another durable store; complete results use digest-bound pieces.
Envelope coverage counts the complete matching source before display limits.
Hot, limited and thin views report their final displayed count separately;
display truncation alone does not create a source omission or alert. Exact
detail reads count their returned Todo rather than the removed list view.
Remaining historical/source adapters still name their actual IO and crossings.
Python source codecs and transport now share the existing
`todos/goal_todo_projection.py` module. The separate Python `read_projection.py`
is removed; the independent py/ts twin budget stays at 43. Current host oracle
observations remain with the existing acceptance owner.
The unchanged 18-row Todo output matrix adds 783–854 JSON characters for source
freshness and omitted-work coverage; Markdown is unchanged. Only the first
envelope schema transition receives a 896-character allowance on the three
measured Todo query views. Existing absolute ceilings and later growth limits
remain in force. These are controlled-transport output measurements.
Source/controlled-IPC evidence does not qualify normal installed CLI/Host use,
full-command performance, R3 adoption or D2/D3/default delivery.


<a id="current-delivery-frontier-2026-10-02"></a>

## Current delivery frontier (post-merge, 2026-10-16)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,36 @@ Python 保留有界地址扫描、请求者注册检查和实时 `Delegations.re
每页复用既有代码版本请求作用域;操作记录仍实时读取,下一页重新解析当前代码。


### Todo 读取事务源码检查点

`todo.read` 现由 `todos/read_projection.ts` 与 `todos/read_transaction.ts`
组合 canonical provider 读取、完整源上的恢复与后继证据、standing decisions、
两个角色的摘要、筛选与公共结果。普通 canonical 查询使用一次请求。
大结果的连续页保留同一次观察和结果摘要;结果变化即拒绝,不拼接新旧页。
依赖与闭合判断仍使用完整源。
Python 保留 registry、文档/材料 IO、独立的 Vision-wait 消费者及兼容导出。
被替代的摘要、筛选与结果协调已退役。持久化 completion 拼写使用共享 token 数据与兼容 codec,
不再逐标量调用 RPC;实际生产调用者退出后,退役 `todo.summary.project`、
`todo.completion_state.normalize` 与 `todo.completion_state.require_metadata`。
事务在进程内执行既有语义 owner。

CLI 查询结果携带查询范围内的 `projection_envelope`。读取不授予执行、验收、
租约或修改权限。共享 wire 与私有快照预算不变。超大 Python 事实使用私有临时
源,不增加持久化存储;完整结果通过摘要绑定的片段传输。
信封覆盖计数使用显示限制前的完整匹配源。hot、限量及 thin 视图分别报告最终
显示数量;显示截断本身不产生来源遗漏或告警。精确详情按返回的 Todo 计数,
不按已移除的列表视图计数。
仍保留的历史/源适配明确记录实际 IO 与跨运行时调用。
Python 源 codec 与传输现在共用既有 `todos/goal_todo_projection.py` 模块,
删除独立的 Python `read_projection.py`,独立维护的 py/ts 配对预算仍为 43。
当前宿主 oracle 观测仍归既有 acceptance owner。
同一 18 行 Todo 输出矩阵为来源新鲜度和遗漏工作覆盖增加 783–854 个 JSON 字符,
Markdown 不变。仅三个已测 Todo 查询视图的首次信封 schema 迁移获得 896 字符
余量;既有绝对上限与后续增长限制继续生效。这些是受控传输下的输出测量。
源码/受控 IPC 证据不代表正常安装的 CLI/Host
使用、完整命令性能、R3 采用或 D2/D3/默认交付资格。


<a id="当前交付边界2026-10-02"></a>

## 当前交付边界(合入后,2026-10-16)
Expand Down
23 changes: 16 additions & 7 deletions docs/reference/todo-work-counts.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ new provider setting: legacy inputs and promoted File/SQLite inputs share the
same typed summary owner. PostgreSQL uses the same provider-neutral records;
service deployment and whole-Goal promotion remain separately qualified.

The list's `projection_envelope` also separates source coverage from display.
Its `included_count` covers all query matches; `shown_count` counts the final
ordinary, hot, limited, thin or exact view. Display truncation alone does not
raise `incomplete_coverage`; see the [envelope contract](contracts/projection-envelope-contract.md).

| Field | Meaning |
| --- | --- |
| `open` | Nonterminal source rows, including blocked work; it is not executable work |
Expand Down Expand Up @@ -51,8 +56,9 @@ are lower bounds while `complete=false`. Current capability re-evaluation
replaces prior readiness for observed rows and cannot certify unseen rows.

The TypeScript `todos/summary_lanes.ts` owner returns indexes into the one input
array instead of repeating full Todo bodies for each lane. The Python adapter
normalizes legacy fields/timestamps and validates the returned ordinal bounds.
array instead of repeating full Todo bodies for each lane. The native read owner
normalizes canonical fields/timestamps and materializes the selected records.
Python retains the legacy source codecs.
One observation time governs Monitor due and expiry classification within the
batch. Source order, completed/deferred conventions and claimant visibility
remain compatible; `done_count` still includes deferred rows as required by its
Expand Down Expand Up @@ -92,11 +98,14 @@ 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. One internal `todo.summary.project` batch now composes selection, counts,
visibility allocation and closure. Its transient request replaces the separate
lane and closure RPC calls; it does not change persisted Todo or public summary
schemas. Python decodes legacy input, validates source ordinals and materializes
public fields, with no independent summary count, cap or claimant-allocation rule.
filters do not recompute dependencies from their smaller view. The `todo.read` transaction composes source evaluation, selection, counts,
frontier identity, handoff context and result construction through the existing
TypeScript owners. Canonical provider IO runs in that transaction; Markdown
source codecs and material IO remain explicit Python adapters. Filtering reuses
validated full-source evidence, never dependencies inferred from a small view.
The old `todo.summary.project` transport and Python ordinal materializer are
retired. Large source/result transport preserves complete decisions and refuses
changed continuations; display limits still disclose shown and available counts.

Route-continuation visibility also uses the existing typed quota planning batch.
It shares the normal claim/exclusion rule; eligibility, first-identity deduplication,
Expand Down
26 changes: 11 additions & 15 deletions loopx/capabilities/periodic_report/post_writeback_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
build_goal_frontier_projection_from_summaries,
)
from .todo_source import read_report_source_history, read_report_todo_source
from ...control_plane.effect_runtime import (
CANONICAL_AUTHORITY_READ_TIMEOUT_SECONDS,
effect_runtime_result,
)
from ...control_plane.todos.quota_summary import summarize_user_todos_for_quota
from ...control_plane.todos.todo_index import MAX_TODO_INDEX_ROLLOUT_EVENTS_PER_GOAL
from ...history import collect_history, load_index_snapshot, load_registry
Expand Down Expand Up @@ -202,18 +198,18 @@ def build_periodic_report_post_writeback_projection(
if available_capabilities is None and isinstance(payload.get("turn"), Mapping):
available_capabilities = payload["turn"].get("available_capabilities")
if source_todos is None and "todo_source" in payload:
source_todos = effect_runtime_result(
"coordination.local_authority.todo_source",
{
"schema_version": "loopx_local_coordination_todo_source_request_v0",
"runtime_root": str(runtime_root), "goal_id": goal_id,
"source": payload["todo_source"],
},
timeout=CANONICAL_AUTHORITY_READ_TIMEOUT_SECONDS,
large_local_snapshot=True,
)
from ...control_plane.todos.goal_todo_projection import project_todo_read, restore_todo_field_evaluations
from ...control_plane.runtime.time import now_utc

now = now_utc()
readback = project_todo_read({"kind": "historical_fields",
"source": {"runtime_root": str(runtime_root), "goal_id": goal_id, "original_source": payload["todo_source"]},
"rollout_events": [], "available_capabilities": available_capabilities,
"observed_at": now.timestamp(), "evaluated_at": now.isoformat()})
source_todos = readback["canonical"]
if source_todos.get("status") != "loaded":
raise ValueError("original canonical Todo source is unavailable")
source_todos["_todo_fields"] = restore_todo_field_evaluations(readback)
source_runs = read_report_source_history(
runtime_root=runtime_root, goal_id=goal_id, run=payload,
)
Expand All @@ -228,7 +224,7 @@ def build_periodic_report_post_writeback_projection(
state.get("path") if isinstance(state, Mapping) else None
) or payload.get("state_file")
if source_todos is not None:
fields = canonical_todo_summary_fields(
fields = source_todos.get("_todo_fields") or canonical_todo_summary_fields(
source_todos["todos"],
rollout_events=events,
available_capabilities=available_capabilities,
Expand Down
12 changes: 3 additions & 9 deletions loopx/capabilities/periodic_report/todo_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from typing import Any

from ...control_plane.coordination.local_authority import (
canonical_todo_summary_fields,
read_canonical_todos_if_promoted,
)
from ...control_plane.todos.active_state_todo_parser import parse_active_state_todos
Expand Down Expand Up @@ -62,16 +61,11 @@ def read_report_todo_source(
registry, None, registry_path=registry_path
)
canonical = read_canonical_todos_if_promoted(
runtime_root=runtime_root, goal_id=goal_id
runtime_root=runtime_root, goal_id=goal_id, include_summary=True,
rollout_events=rollout_events, available_capabilities=available_capabilities
)
if canonical is not None:
fields = canonical_todo_summary_fields(
canonical["todos"],
rollout_events=rollout_events,
available_capabilities=available_capabilities,
goal_acceptance_contract=canonical.get("goal_acceptance_contract"),
goal_acceptance_work_guards=canonical.get("goal_acceptance_work_guards"),
)
fields = canonical["_todo_fields"]
return fields, [row for row in canonical["todos"] if row.get("role") == "user"]
state_path = state_path or resolve_state_file(
Path(str(goal.get("repo") or "")).expanduser(),
Expand Down
2 changes: 2 additions & 0 deletions loopx/cli_commands/todo_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def handle_todo_list_command(
project=Path(args.project).expanduser() if args.project else None,
state_file=Path(args.state_file).expanduser() if args.state_file else None,
runtime_root_arg=runtime_root_arg,
json_references=(format_name or str(getattr(args, "format", None) or "markdown")) == "json",
)
except Exception as exc:
from ..usage_ping import capture_failure
Expand All @@ -56,6 +57,7 @@ def handle_todo_list_command(
)
output_format = format_name or str(getattr(args, "format", None) or "markdown")
if (payload.get("ok") and not args.todo_id and not args.todo_thin
and "todo_list_record_references" not in payload
and output_format == "json"):
try:
payload = effect_runtime_result(
Expand Down
4 changes: 2 additions & 2 deletions loopx/control_plane/collaboration/conversation_binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ function planContextRecovery(params: JsonObject, row: JsonObject, current: JsonO
|| !["context_window_exceeded", "host_gate", "upstream_transport_failure", "server_overloaded"].includes(String(failed.error_code))
|| typeof failed.upstream_turn_id !== "string" || !failed.upstream_turn_id) return no;
const previous = row.context_recovery == null ? null : requireJsonObject(row.context_recovery, "reserved context recovery");
if (recovery.admitted_turn != null && previous?.failed_turn_id === failed.turn_id
if (recovery.admitted_turn != null && previous !== null && previous.failed_turn_id === failed.turn_id
&& Number.isSafeInteger(previous.routing_attempt) && Number(previous.routing_attempt) >= 1
&& Number(previous.routing_attempt) <= 3 && previous.version === version) {
// A crash after queue admission must adopt that exact canonical Turn,
Expand Down Expand Up @@ -560,7 +560,7 @@ function planRoutingRecovery(recovery: JsonObject, row: JsonObject, current: Jso
failed: JsonObject, previous: JsonObject | null, version: number): JsonObject | null {
if (!["host_gate", "upstream_transport_failure"].includes(String(failed.error_code))) return null;
// An unfinished older reservation must finish under its original identity.
if (previous?.failed_turn_id === failed.turn_id && previous.routing_attempt === undefined) return null;
if (previous !== null && previous.failed_turn_id === failed.turn_id && previous.routing_attempt === undefined) return null;
const observed = recovery.native_observation == null ? null
: requireJsonObject(recovery.native_observation, "native failure readback");
if (observed && observed.pre_execution_routing_timeout !== true) return null;
Expand Down
Loading
Loading