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 @@ -1231,6 +1231,17 @@ The TurnEnvelope owner retains the top-level decision-field vocabulary and
supplies it to Progressive selection. Moving the selector does not duplicate
that vocabulary across owners or relax its semantic module budget.

Native Progressive supporting-material discovery now has a bounded precursor:
an unconfirmed index binds exact deferred-field pointers to the current complete
projection. Initial reads and file-backed warm deltas expose the complete index,
without requiring an old packet or model retention. Selected reads do not clear
its conservative coverage status or create a second ledger. Current-source CLI
reads qualify transport into a synthetic next artifact, including changed and
reordered material and unavailable predecessors; they do not establish natural
model adoption. All-host defaults, other omitted material, trusted Host coverage,
writer/reader cutover and §9's outcome/full-cost comparison remain open. Index
overhead participates in the existing full-versus-folded byte guard.

C1's inventory is part of its usable replacement, not a standalone architecture
framework. C3/C4/C5 can proceed independently once C1's contract is stable;
C7 can prepare measurements before C6 but cannot claim two-session outcomes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,13 @@ backoff 继续归当前 scheduler owner。本文不引入模型空轮询或新
顶层 decision 字段清单保留在 TurnEnvelope owner,由其传给 Progressive 选读;迁移
selector 不跨 owner 重复声明该词汇,也不放宽语义模块预算。

原生 Progressive 支持材料发现增加有界前置实现:保守未确认索引将延迟字段的精确
指针绑定当前完整 projection,首读和文件 warm delta 均完整呈现,不依赖旧包或模型
retention。选读不会清除覆盖未知状态或新增第二账本。当前源 CLI 读取覆盖变化、重排
及前序不可用,并进入合成后继产物;这只资格化运输,不证明自然模型采用。跨宿主默认、
其他省略材料、可信 Host 覆盖、writer/reader 切换及第9节成果/完整成本对照仍开放。
索引开销计入原完整/折叠字节守卫。

C1 盘点是可用替换的一部分,不单独建设架构框架。C1 契约稳定后,C3/C4/C5 可以独立
推进;C7 可以提前准备测量,但不能提前宣称双会话成果。实现 PR 列明替换的调用方、剩余
缺口、实际测试与回退。完整语言迁移或无关 manager 重写不是前置条件;新抽象能编译
Expand Down
32 changes: 32 additions & 0 deletions docs/reference/required-work-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,38 @@ Agent、所选 Todo、成果版本及 provider revision 一致时,首读完整
原索引/详情路线;折叠的支持材料仍遵守原读取义务。此选读使用无需 Session Hook 的
通用 reader,不改变启用默认值、落盘文件、Delta 或完整读取回执。

For supporting fields folded by the existing native Progressive owner,
`context_details.unread_context_index` contains the current complete
`projection.json` source path/digest and exact `pointers`. The initial reader
shows that index; a file-backed warm delta repeats it in full even when its
pointers are unchanged. Each pointer uses the ordinary `context-read`
`--capture-file`, `--capture-sha256` and `--select` route, independently of any
previous packet, retained model input or patch chain. These addresses belong to
the complete source, not the derived `required-context.json` recovery view.

`coverage_confirmed=false` conservatively reports no trusted model-visible
coverage witness. A previous select, reader EOF or file receipt does not clear
an entry or create `confirmed_read_sections`/`consumed_pointers` state.
`read_before_dependent_work` preserves the original obligation to read material
when the decision or verification needs it; it is not an automatic full-read or
execution gate. Updated/reordered material binds to its current addresses;
removed material does not retain a predecessor route. The actual index and
guidance count toward the existing byte guard, including fallback to the
complete file when folding is not smaller. In-view deduplication alone adds no
index. This bounded native slice changes no activation defaults or off path;
cross-host rollout and trusted coverage observation remain C1/C2 work.

中文:现有原生 Progressive owner 折叠的支持字段,在
`context_details.unread_context_index` 中列出当前完整 `projection.json` 的路径、
摘要和精确 `pointers`。首读呈现该索引;文件 warm delta 即使指针未变也完整重发。
各指针通过普通 `context-read --capture-file ... --capture-sha256 ... --select ...`
独立读取,不依赖旧包、模型保留或补丁链;它们属于完整源,不属于派生 required view。
`coverage_confirmed=false` 表示没有可信模型可见覆盖见证,选读、EOF或文件回执不会
清除条目,也不新建已读账本。`read_before_dependent_work` 保留原先按决策/验证需要
读取的义务,不成为自动全读或执行门。变化/重排绑定当前地址,移除材料不沿用前序入口。
索引和指导计入原字节守卫,不更小时回退完整文件;仅视图内去重不新增索引。本原生切片
不改启用默认值或 off 路径,跨宿主推广和可信覆盖观察仍属 C1/C2 未完成工作。

Checkpoint Host preflight uses the hidden `--defer-context-presentation` option
with an explicitly enabled native JSON `should-run` capture. It collects the
same complete Goal source for admitted work-context reads and writes the ordinary
Expand Down
6 changes: 4 additions & 2 deletions loopx/control_plane/quota/context_delivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ export function prepareContextDelivery(request: JsonObject, legacyQuotaFields: r
/** Shared encoding within one presentation transaction; callers publish once
* against their original source so numeric facts never round-trip through JS. */
export function encodeContextDelivery(request: JsonObject, legacyQuotaFields: readonly string[],
values: ProjectionSourceValues): JsonObject {
values: ProjectionSourceValues, fileNavigation: Path[] = []): JsonObject {
const context = requireJsonObject(request.context, "current projection");
const identity = contextSourceIdentity(request, request.source_number_tokens);
const surface = surfaceOf(request.surface ?? "work_context"), target = targetBinding(request, context, surface);
Expand All @@ -612,7 +612,9 @@ export function encodeContextDelivery(request: JsonObject, legacyQuotaFields: re
if (!Number.isSafeInteger(depth) || Number(depth) < 0 || Number(depth) >= MAX_ANCESTRY) return fullResult("full_refresh_due");
const before = freshPaths(prior, surface, legacyQuotaFields, binding), after = freshPaths(context, surface, legacyQuotaFields, target);
if (!fileBacked && (!before.valid || !after.valid)) return fullResult("source_not_reusable");
const changes = changeTree(prior, context, fileBacked ? [] : after.paths, fileBacked,
// The presentation owner may require navigation without opening the base.
// This does not turn file continuity into confirmed model delivery.
const changes = changeTree(prior, context, fileBacked ? fileNavigation : after.paths, fileBacked,
{identity, before: ["baseline", "context"], after: ["context"],
...(request.source_edits === true ? {values} : {})});
const delta: JsonObject = {schema_version: VERSION, mode: "delta", target, base: binding,
Expand Down
20 changes: 17 additions & 3 deletions loopx/control_plane/quota/progressive_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {quotaSummaryReferences, contextSourceIdentity, contextProjectionChanges,
import {encodeContextDelivery} from "./context_delivery.ts";

type Path = (string | number)[];
const pointer = (path: Path): string => "/" + path.map(key =>
String(key).replaceAll("~", "~0").replaceAll("/", "~1")).join("/");
const at = (root: unknown, path: Path): unknown => path.reduce<unknown>((row, key) =>
Array.isArray(row) ? row[Number(key)] : jsonObject(row)?.[key], root);
const known = (row: JsonObject, fields: string[]): boolean => Object.keys(row).every(key => fields.includes(key));
Expand Down Expand Up @@ -138,21 +140,29 @@ function requiredNativeContext(request: JsonObject, sourceValues: ProjectionSour
for (const edit of deferred) {
const path = edit.path as Path, owner = edit.parent as Path;
delete parent(path)[path.at(-1)!];
parent([...owner, String(edit.index_key)])[String(edit.index_key)] = "/" + owner.map(key =>
String(key).replaceAll("~", "~0").replaceAll("/", "~1")).join("/");
parent([...owner, String(edit.index_key)])[String(edit.index_key)] = pointer(owner);
removed.push(["context", ...path]);
}
for (const edit of instructions) {
const path = edit.path as Path;
parent(path)[path.at(-1)!] = edit.value;
}
if (deferred.length) required.context_details = {...details,
unread_context_index: {
source: {path: details.path, sha256: details.sha256},
coverage_confirmed: false,
read_before_dependent_work: true,
pointers: [...new Set(deferred.map(edit => pointer(edit.path as Path)))],
},
instruction: "This view retains complete requirements and current review/check states. "
+ "Source history, review reasoning and evidence references are supporting material. "
+ "Read them from this same immutable capture when a decision, verification or review depends on them. "
+ "For source history use supporting_material_pointer plus /sources. For review detail append "
+ "/requirements/<index>/agent_review/note or /requirements/<index>/agent_review/evidence_refs instead. "
+ "Repeat --select to batch needed fields without replaying the outcome's requirements. "
+ "unread_context_index lists unconfirmed supporting fields in the current complete source. "
+ "Use its source path and sha256 with quota context-read --select for each exact pointer. "
+ "A prior selected read, EOF or saved file does not confirm model-visible coverage or retention. "
+ "Detail reads never establish a full baseline. "
+ "Uncertain or missing necessary facts must be recovered before dependent action; Agent review is not independent acceptance."};
}
Expand Down Expand Up @@ -189,7 +199,10 @@ export function prepareProgressiveContextDelivery(request: JsonObject, legacyFie
const required = requiredNativeContext(request, sourceValues);
const codec = nativeContextContinuity({...request, context: required.context,
...(request.source_number_tokens === undefined ? {} : {source_number_tokens: required.tokens})});
const delivery = encodeContextDelivery(codec, legacyFields, sourceValues);
const details = jsonObject(required.context.context_details);
const navigation: Path[] = details && Object.hasOwn(details, "unread_context_index")
? [["context_details", "unread_context_index"]] : [];
const delivery = encodeContextDelivery(codec, legacyFields, sourceValues, navigation);
const {operation: _operation, source_edits: _edits, source_number_tokens: _numbers, ...source} = request;
return contextProjectionChanges(source, {context: required.context, delivery, projected: required.projected}, sourceValues);
}
Expand All @@ -210,6 +223,7 @@ export function initialContextReadPointers(context: JsonObject, decisionFields:
pointers.push("/" + path.map(key => String(key).replaceAll("~", "~0").replaceAll("/", "~1")).join("/"));
};
for (const field of decisionFields) include(field);
include("context_details", "unread_context_index");
include("agent_lane_next_action", "next_step");
include("agent_lane_next_action", "next_action_basis");
include("interaction_contract", "user_channel");
Expand Down
3 changes: 2 additions & 1 deletion tests/control_plane/test_context_first_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ def test_initial_read_preserves_empty_and_unknown_current_preference_values(tmp_
page, values = read_selection(view["first_read_command"])
pointer = f"/interaction_contract/agent_channel/work_context/sources/{len(sources) - 1}/content/current"
assert values[pointer] == current
assert not any(key.startswith(pointer + "/") for key in values) # No duplicate delivered descendants.
pointers = {row["pointer"] for row in json.loads(page["content"])["selections"]}
assert not any(key.startswith(pointer + "/") for key in pointers) # No duplicate delivered descendants.
raw = Path(view["recovery"]["path"]).read_bytes()
assert json.loads(raw) == before == decision
assert capture_page_range(page, raw=raw, digest=page["sha256"]) is None
Expand Down
126 changes: 126 additions & 0 deletions tests/control_plane/test_context_unread_reachability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Current-capture detail discovery stays separate from delivery and retention."""
import argparse
import copy
import json
from pathlib import Path
import shlex

import pytest

from loopx.cli_commands.quota_capture import read_context_capture
from loopx.control_plane.turn_driver import codex_context as context
from test_codex_context_lifecycle import setup, project
from test_progressive_native_context import payload, outcome
from test_quota_settlement_cli import _run_cli


@pytest.mark.parametrize("previous_detail_read", [False, True])
@pytest.mark.parametrize("change", ["unchanged", "updated", "reordered"])
def test_unconfirmed_material_reaches_next_turn_work_from_current_capture(
tmp_path, monkeypatch, previous_detail_read, change,
):
from loopx.cli_commands.quota_capture import native_context_capture_view

setup(tmp_path, monkeypatch)
original = payload()
original["interaction_contract"] = {"schema_version": "loopx_interaction_contract_v0",
"agent_channel": {"work_context": {"sources": []}}}
# Necessary inline text makes a file delta useful independently of the
# heavy supporting material whose delivery has never been confirmed.
outcome(original)["requirements"][0]["description"] = "Keep this whole acceptance clause. " * 300
outcome(original)["requirements"][0]["agent_review"]["note"] += "Tail constraint: keep the report local."
peer = copy.deepcopy(outcome(original))
peer["id"] = "peer"
peer["owner_agent_id"] = "other-agent"
original["planning_horizon"]["core_goal"]["acceptance_contract"]["outcome_requirements"]["outcomes"].append(peer)
_, first = project(tmp_path, payload=original)
first_details = first["context"]["context_details"]
first_index = first_details["unread_context_index"]
assert first_index["coverage_confirmed"] is False
assert first_index["source"] == {key: first_details[key] for key in ("path", "sha256")}
registry, runtime = tmp_path / "no-registry.json", tmp_path / "no-runtime"
navigation = native_context_capture_view(first, Path(first["recovery"]["path"]).parent,
context_directory=str(tmp_path))
argv = shlex.split(navigation["first_read_command"])
code, initial = _run_cli(registry, runtime, *argv[argv.index("quota"):], cwd=tmp_path)
assert code == 0 and initial["eof"], initial
shown = {row["pointer"]: row["value"] for row in json.loads(initial["content"])["selections"]}
assert shown["/context_details/unread_context_index"] == first_index

def read_detail(source, pointer):
words = ["quota", "context-read", "--capture-file", source["path"],
"--capture-sha256", source["sha256"], "--select", pointer,
"--page-bytes", "1200", "--max-page-output-bytes", "2600"]
body, offset = [], 0
while True:
code, page = _run_cli(registry, runtime, *words, cwd=tmp_path)
assert code == 0 and page["ok"] and page["offset"] == offset, page
body.append(page["content"])
if page["eof"]:
return json.loads("".join(body))["selections"][0]["value"]
offset = page["next_offset"]
argv = shlex.split(page["next_command"])
words = argv[argv.index("quota"):]

if previous_detail_read:
previous_note = next(pointer for pointer in first_index["pointers"]
if pointer.endswith("/outcomes/0/requirements/0/agent_review/note"))
assert read_detail(first_index["source"], previous_note).endswith("keep the report local.")
current = copy.deepcopy(original)
if change == "updated":
outcome(current)["requirements"][0]["agent_review"]["note"] += " Updated constraint: include the contrary result."
if change == "reordered":
current["planning_horizon"]["core_goal"]["acceptance_contract"]["outcome_requirements"]["outcomes"].reverse()
_, warm = project(tmp_path, "next-turn", payload=current)
assert warm["mode"] == "delta"
required = json.loads(Path(warm["recovery"]["path"]).read_bytes())
index = required["context_details"]["unread_context_index"]
assert index["coverage_confirmed"] is False
assert Path(index["source"]["path"]).name == "projection.json"
assert index["source"]["path"] != first_index["source"]["path"]

def literal_index(value):
if isinstance(value, dict):
found = value.get("unread_context_index")
if found == index or isinstance(found, dict) and found.get("value") == index:
return True
return any(literal_index(child) for child in value.values())
return isinstance(value, list) and any(literal_index(child) for child in value)

assert literal_index(warm["changes"]), "Warm discovery must not require an inherited index."
rows = current["planning_horizon"]["core_goal"]["acceptance_contract"]["outcome_requirements"]["outcomes"]
position = next(i for i, row in enumerate(rows) if row["id"] == "outcome")
pointer = (f"/planning_horizon/core_goal/acceptance_contract/outcome_requirements/outcomes/{position}"
"/requirements/0/agent_review/note")
assert pointer in index["pointers"]
peer_position = next(i for i, row in enumerate(rows) if row["id"] == "peer")
assert (f"/planning_horizon/core_goal/acceptance_contract/outcome_requirements/outcomes/{peer_position}/sources"
in index["pointers"])
# No previous packet, retained model input or patch chain is available to
# this no-hook reader. Its actual next artifact must use the recovered tail.
Path(first["recovery"]["path"]).unlink()
Path(first_details["path"]).unlink()
material = read_detail(index["source"], pointer)
artifact = tmp_path / "next-turn-report.txt"
artifact.write_text(material)
assert artifact.read_text() == rows[position]["requirements"][0]["agent_review"]["note"]
assert "Tail constraint: keep the report local." in artifact.read_text()
assert not {"confirmed_read_sections", "consumed_pointers"} & context._state(tmp_path).keys()
assert not registry.exists() and not runtime.exists()


def test_removed_material_does_not_keep_a_predecessor_discovery_route(tmp_path, monkeypatch):
setup(tmp_path, monkeypatch)
original = payload()
_, first = project(tmp_path, payload=original)
assert first["context"]["context_details"]["unread_context_index"]["pointers"]
current = copy.deepcopy(original)
outcome(current)["sources"] = []
outcome(current)["requirements"][0].pop("agent_review")
_, next_turn = project(tmp_path, "removed", payload=current)
recovery = next_turn["recovery"]
assert json.loads(Path(recovery["path"]).read_bytes()) == current
missing = read_context_capture(argparse.Namespace(capture_file=recovery["path"],
capture_sha256=recovery["sha256"], select=["/context_details/unread_context_index"],
offset=0, page_bytes=12000, max_page_output_bytes=18000, format="json"))
assert missing["ok"] is False
Loading