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
30 changes: 20 additions & 10 deletions docs/reference/required-work-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,13 @@ model retention. These separate boundaries prevent transport loss from becoming
either a repeated full-input loop or permission to act on missing requirements.
No token savings or accepted outcome gains follow from instruction delivery alone.

An enabled native **full** reference for a recognized decision can include
`first_read_command`. It batches current admission/stop facts, the selected task,
An enabled native **full or delta** reference for a recognized decision can include
`first_read_command`, reading its current full capture without an older baseline.
It batches current admission/stop facts, the current action, the selected task,
whole Goal/Todo narrative, source availability/revisions and read routes. Detailed
history and optional authoring recipes remain expandable from the same capture.
Current narrative sources and their provenance precede other sources, including
large scoped preferences; both retain their complete selected values.
The preference source's complete `current` view also stays in the initial read:
corrections, retirement/expiry and empty replacements retain their provenance,
revision and advisory authority. This captured pre-work view does not replace the
Expand Down Expand Up @@ -207,8 +210,10 @@ legacy readers still require reconciliation of their unfinished original Turns
before retirement; this slice retains their decoder and full-capture recovery.
Initial navigation can replace fully
selected children with their complete captured parent to avoid repeating addresses.
An unselected child, including optional or unknown material, prevents that merge;
the selected facts and obligations stay identical. Explicit selectors and index
An unselected child, including optional or unknown material, prevents that merge.
An array also stays separate when merging would restore an earlier element order
and undo the owner's reading priority; ancestors cannot erase that boundary.
The selected facts and obligations stay identical. Explicit selectors and index
entries keep their original addresses. The option is read-only and exclusive with explicit
selectors or discovery; unknown schemas use generic discovery.
Pinned explicit references replay their recorded address order after the same
Expand All @@ -231,8 +236,9 @@ This reorders the same selected facts without reducing the total body or
guaranteeing that all requirements fit one page. Earlier initial-selection
digests still reject a changed profile; recover from the original full capture.
The concrete first read replaces the broad scalar preview and root index when it
fits; unknown schemas or a tight reference budget keep generic discovery. Warm
deltas keep their existing reader, and disabled native delivery is unchanged.
fits; unknown schemas or a tight reference budget keep generic discovery. A warm
delta's existing `command` still reads its wire delta; its initial read and
recovery read current facts directly. Disabled native delivery is unchanged.
This is a starting selection, not all required sources: read applicable criteria,
evidence and the complete operation/settlement contract before depending on them.
The original SHA-bound full recovery remains available; a selected page's EOF
Expand All @@ -249,22 +255,26 @@ index/detail route. Folded supporting material still has its original read
obligations. The generic reader needs no Session Hook for this selection;
activation defaults, saved files, Delta and full-read receipts are unchanged.

中文:已启用原生传输的完整引用可给出 `first_read_command`,合并当前准入、停止条件、
任务和完整 Goal/Todo 正文,以及来源状态、版本与读取入口;历史和可选格式说明仍在
中文:已启用原生传输的 full 或 delta 引用可给出 `first_read_command`,直接读取本次
完整捕获,无须先读旧基线。首读合并当前准入、停止条件、动作、任务和完整 Goal/Todo
正文,以及来源状态、版本与读取入口;正文来源及其 provenance 先于其他来源,
包括较大的 scoped preferences,双方完整选中值均保留。历史和可选格式说明仍在
同一捕获中按需展开。它是起始选读,不代表已读全部必要来源;依赖具体验收、证据或
执行/结算契约前仍须完整读取。
偏好来源的完整 `current` 视图也直接呈现:纠偏、退役/过期和空视图替换均保留
原来源、版本及 advisory 权限边界。本次捕获的工作前视图不能替代偏好 owner 要求的
后续偏好相关外部动作前精确作用域新读;偏好及其交付均不授予权限。
未知 schema、过小引用预算和 warm delta 保留既有路线,
未知 schema 和过小引用预算保留既有路线;warm delta 的 `command` 仍读取原 wire delta,
首读和恢复入口直接读取当前事实。
关闭原生传输的输出不变;完整恢复和原 SHA 校验均保留。只读的 `--initial-read` 不与
显式选读或索引混用。新私有选读引用固定已解析路径和完整选读摘要,续页复用显式
reader,不重新运行首读导航;后续导航改变不能替换原选读。历史仅保存导航配方的引用
及显式文件首读游标在选读摘要变化时仍拒绝,沿同一完整原 capture 引用恢复,不重新
准入或改写旧描述文件。退役这些 legacy reader 前仍须对账原未完成 Turn;本切片
保留历史解码与完整捕获恢复。
首读可把已经完整选中的同级内容合并为其完整父节点,减少地址重复;任何未选中的
可选或未知内容都会阻止合并,选中事实和义务不变。显式选读和索引仍保留原地址。
可选或未知内容都会阻止合并;若数组合并会恢复原元素顺序、覆盖 owner 的读取优先级,
该数组及其祖先也不合并。选中事实和义务不变。显式选读和索引仍保留原地址。
固定显式引用在既有规范地址校验后,按记录的地址顺序重放。旧描述符若保存调用方顺序
却绑定规范排序正文的摘要,仅在摘要不匹配后,对同一已验证来源及相同地址集合重试
一次原规范 planner;只返回摘要完全匹配的字节。此兼容路径每个旧分页最多增加一次
Expand Down
5 changes: 3 additions & 2 deletions loopx/cli_commands/quota_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,11 @@ def _native_context_reference(projected: dict[str, Any], reference: dict[str, An
# The reference must fit even under an explicit small caller ceiling.
def fits() -> bool:
return len(json.dumps(result, ensure_ascii=False, separators=(",", ":")).encode()) + 1 <= min(output_bytes, 7900)
if initial_read_pointers and mode == "full":
if initial_read_pointers:
overview = result.pop("overview")
instruction = result["instruction"]
result["instruction"] = ("Start with first_read_command for current facts and source navigation. "
result["instruction"] = ("Start with first_read_command for current facts and source navigation from this capture, "
"without reading an older baseline. "
"Read each complete optional attachment from its captured step before using it. " + instruction)
result["first_read_command"] = context_capture_read_command(current_path, current["sha256"],
page_bytes=page_bytes, max_page_output_bytes=output_bytes, initial_read=True)
Expand Down
7 changes: 6 additions & 1 deletion loopx/control_plane/quota/context_selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,13 @@ function coalescedInitialPointers(context: JsonObject, pointers: string[]): stri
return {complete: false, pointers: []};
const children = Object.entries(value).map(([key, child]) =>
visit(child, pointer + "/" + key.replaceAll("~", "~0").replaceAll("/", "~1")));
const complete = children.length > 0 && children.every(child => child.complete);
const fields = children.flatMap(child => child.pointers);
// A whole array replays its original element order. Keep prioritized
// elements separate when merging would undo the owner's navigation order;
// propagate that boundary so an ancestor cannot merge them back either.
const preservesOrder = !Array.isArray(value) || fields.every((field, index) =>
index === 0 || positions.get(fields[index - 1])! < positions.get(field)!);
const complete = children.length > 0 && children.every(child => child.complete) && preservesOrder;
// A single child needs no wrapper; grouping it can increase output bytes.
return {complete, pointers: complete && fields.length > 1 ? [pointer] : fields};
};
Expand Down
11 changes: 10 additions & 1 deletion loopx/control_plane/quota/progressive_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,22 @@ export function initialContextReadPointers(context: JsonObject, decisionFields:
include("agent_lane_next_action", "next_step");
include("agent_lane_next_action", "next_action_basis");
include("interaction_contract", "user_channel");
for (const field of ["primary_action", "delivery_allowed", "must_attempt", "quiet_noop_allowed"])
include("interaction_contract", "agent_channel", field);
include("interaction_contract", "agent_channel", "required_reads");
const work = ["interaction_contract", "agent_channel", "work_context"];
include(...work, "instruction");
include(...work, "user_todos");
const channel = object(interaction.agent_channel);
const sources = object(channel?.work_context)?.sources;
if (Array.isArray(sources)) sources.forEach((_source, index) => {
const sourceRows = Array.isArray(sources) ? sources.map((source, index) => ({source, index})) : [];
// A complete scoped preference can be larger than a reader page. Visit
// current narrative sources first, with their availability and provenance,
// before selecting other sources' metadata: complete-parent coalescing keeps
// the first selected child's position. Preserve stable order within each group.
const hasText = (source: unknown): boolean => typeof object(object(source)?.content)?.text === "string";
sourceRows.sort((left, right) => Number(hasText(right.source)) - Number(hasText(left.source)));
sourceRows.forEach(({source: _source, index}) => {
const source = [...work, "sources", index];
for (const field of ["source", "kind", "reason", "ordering", "command"])
include(...source, field);
Expand Down
89 changes: 86 additions & 3 deletions tests/control_plane/test_context_first_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import pytest

from loopx.cli_commands.quota_capture import native_context_capture_view, read_context_capture
from loopx.cli_commands.quota_capture import model_context_reference, native_context_capture_view, read_context_capture
from loopx.cli_commands.quota_capture import handle_native_context_command
from loopx.control_plane.turn_driver import codex_context
from loopx.control_plane.turn_driver.decision_capture import capture_page_range
Expand Down Expand Up @@ -164,6 +164,57 @@ def test_initial_read_coalesces_whole_contract_with_exact_values_and_unknown_obl
assert "older-evidence-" not in page["content"]


@pytest.mark.parametrize("complete_sources", [False, True])
def test_cli_current_goal_and_action_precede_complete_scoped_preferences(tmp_path, decision, complete_sources):
sources = decision["interaction_contract"]["agent_channel"]["work_context"]["sources"]
goal = sources[0]["content"]
goal["text"] = "CURRENT GOAL: compare accepted outcomes; STOP at the original budget."
preferences = {"source": "agent_preferences", "content": {
"status": "loaded", "revision": "preference-current", "source_authority": "advisory_only",
"current": {"items": ["complete scoped preference " * 2000], "future_constraint": False},
}}
sources.insert(0, preferences)
if complete_sources:
# Even when the entire array is selected, merging it back into its
# original order must not move the large preference ahead of the Goal.
goal.pop("full_criteria")
sources[2:] = []
channel = decision["interaction_contract"]["agent_channel"]
channel.update(primary_action="Read the current review before writing back.",
delivery_allowed=False, must_attempt=False, quiet_noop_allowed=False)
view = native_context_capture_view(decision, tmp_path)
raw = Path(view["recovery"]["path"]).read_bytes()
registry, runtime = tmp_path / "absent-registry.json", tmp_path / "absent-runtime"
command = view["first_read_command"]
content, pages = b"", []
while True:
words = shlex.split(command)
for option in ("--page-bytes", "--max-page-output-bytes"):
words[words.index(option) + 1] = "8192"
result = subprocess.run([sys.executable, "-m", "loopx.entrypoint", "--registry", str(registry),
"--runtime-root", str(runtime), "--format", "json", *words[words.index("quota"):]],
cwd=tmp_path, capture_output=True, text=True, check=False)
page = json.loads(result.stdout)
assert result.returncode == 0 and page["ok"] and page["offset"] == len(content), page
assert len(result.stdout.encode()) <= 8192
assert capture_page_range(page, raw=raw, digest=view["recovery"]["sha256"]) is None
pages.append(page)
content += page["content"].encode()
if page["eof"]:
break
command = page["next_command"]
first = pages[0]["content"]
for value in (goal["text"], goal["status"], goal["revision"], goal["recovery"], channel["primary_action"]):
assert value in first
assert first.index(goal["text"]) < first.index("complete scoped preference")
values = selected_context_values(content)
assert values["/interaction_contract/agent_channel/work_context/sources/0"] == preferences
assert values["/interaction_contract/agent_channel/primary_action"] == channel["primary_action"]
assert values["/interaction_contract/agent_channel/delivery_allowed"] is False
assert json.loads(raw) == decision and Path(view["recovery"]["path"]).read_bytes() == raw
assert not registry.exists() and not runtime.exists()


@pytest.mark.parametrize("provider", ["file", "sqlite"])
def test_advertised_native_reader_keeps_the_complete_current_contract_across_pages(tmp_path, monkeypatch, provider):
from test_quota_settlement_cli import (
Expand Down Expand Up @@ -508,7 +559,8 @@ def test_initial_read_is_rejected_before_quota_or_machine_access(tmp_path):
assert not (tmp_path / "unopened-registry.json").exists()


def test_warm_delta_keeps_its_existing_reader_and_current_recovery(tmp_path, monkeypatch, decision):
@pytest.mark.parametrize("opaque", [False, True])
def test_warm_delta_initial_read_uses_current_action_without_the_old_base(tmp_path, monkeypatch, decision, opaque):
tmp_path.chmod(0o700)
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("CODEX_THREAD_ID", "fixture-session")
Expand All @@ -518,10 +570,41 @@ def test_warm_delta_keeps_its_existing_reader_and_current_recovery(tmp_path, mon
directory = tmp_path / name
directory.mkdir(mode=0o700)
decision["agent_lane_next_action"]["next_action_basis"] = marker
decision["effective_action"] = "normal_run" if marker == "before" else "autonomous_replan_required"
decision["interaction_contract"]["agent_channel"].update(
primary_action=f"Read {marker} action.", delivery_allowed=marker == "before")
wire = codex_context.project_codex_context(decision, directory=str(tmp_path), capture_directory=directory,
goal_id="fixture", agent_id="agent", surface="quota_packet", capture_id=name)
view = native_context_capture_view(wire, directory, context_directory=str(tmp_path))
assert view["mode"] == "delta" and "first_read_command" not in view
assert view["mode"] == "delta" and "first_read_command" in view
current = json.loads(Path(view["recovery"]["path"]).read_bytes())
assert current["agent_lane_next_action"]["next_action_basis"] == "after"
assert view["sha256"] != view["recovery"]["sha256"]
baseline = Path(view["baseline_reference"]["path"])
baseline.rename(baseline.with_suffix(".unavailable"))
navigation = model_context_reference(view) if opaque else view
words = shlex.split(navigation["first_read_command"])
registry, runtime = tmp_path / "absent-registry.json", tmp_path / "absent-runtime"
command = [sys.executable, "-m", "loopx.entrypoint", "--registry", str(registry),
"--runtime-root", str(runtime), "--format", "json", *words[words.index("quota"):]]
result = subprocess.run(command, cwd=tmp_path, capture_output=True, text=True, check=False)
page = json.loads(result.stdout)
assert result.returncode == 0 and page["ok"] and page["eof"], page
values = selected_context_values(page["content"])
assert values["/agent_lane_next_action/next_action_basis"] == "after"
assert values["/effective_action"] == "autonomous_replan_required"
assert values["/interaction_contract/agent_channel/primary_action"] == "Read after action."
assert values["/interaction_contract/agent_channel/delivery_allowed"] is False
if opaque:
assert "sha256" not in page
else:
assert page["sha256"] == view["recovery"]["sha256"]
assert "Read before action." not in page["content"]
delta = read_context_capture(Namespace(capture_file=view["path"], capture_sha256=view["sha256"],
format="json", offset=0, page_bytes=64000, max_page_output_bytes=64000))
assert delta["ok"] and json.loads(delta["content"])["mode"] == "delta"
Path(view["recovery"]["path"]).write_text("{}\n")
failed = subprocess.run(command, cwd=tmp_path, capture_output=True, text=True, check=False)
failure = json.loads(failed.stdout)
assert failed.returncode != 0 and not failure["ok"] and "content" not in failure
assert not registry.exists() and not runtime.exists()
17 changes: 16 additions & 1 deletion tests/control_plane_ts/context_selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ test("initial navigation coalesces complete branches without changing selected f
single: {only: "A single address need not grow a container."}, unread: {history: "optional body"}};
// The owner's discovery order differs from object key order. A merged parent
// occupies its first selected child's position, preserving that priority.
const pointers = ["/single/only", "/facts/1/evidence", "/facts/0/a~1b", "/facts/0/~0", "/facts/1/allowed"];
const pointers = ["/single/only", "/facts/0/a~1b", "/facts/0/~0", "/facts/1/evidence", "/facts/1/allowed"];
const before = structuredClone(context);
const explicit = select(context, pointers);
const initial = capturedContextSelection({context, initial_read: true}, pointers);
Expand All @@ -169,6 +169,21 @@ test("initial navigation coalesces complete branches without changing selected f
assert.deepEqual(context, before);
});

test("initial navigation retains prioritized array elements through complete ancestors", () => {
const context = {complete: {facts: [{text: "Supporting evidence."}, {text: "Current Goal and final stop."}]}};
const before = structuredClone(context);
const pointers = ["/complete/facts/1/text", "/complete/facts/0/text"];
const initial = capturedContextSelection({context, initial_read: true}, pointers);
// A complete array or ancestor would replay evidence before the current Goal.
const expected = pointers;
assert.deepEqual((initial.selections as {pointer: string}[]).map(row => row.pointer), expected);
assert.deepEqual(selectedFacts(context, initial.selections as {path: (string | number)[]}[]),
selectedFacts(context, select(context, pointers).selections as {path: (string | number)[]}[]));
const discovery = capturedContextSelection({context, select: [""], list_fields: true}, pointers);
assert.deepEqual(discovery.initial_read_pointers, expected);
assert.deepEqual(context, before);
});

test("ordinary selectors do not inspect initial-navigation data", () => {
const context = {facts: {allowed: false}};
const unused = new Proxy<string[]>([], {get() { throw new Error("Initial navigation was not requested"); }});
Expand Down
Loading