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
8 changes: 7 additions & 1 deletion docs/reference/required-work-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,11 @@ versions remain whole. Initial pages do not repeat pointers already present in
their content; the selection digest still prevents a full-input receipt.
Its `context-read --initial-read` uses the same exact-address selector; paged
continuations retain the short option and pin the selection digest. A changed
profile rejects rather than mixing views. The option is read-only and exclusive with explicit
profile rejects rather than mixing views. 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
entries keep their original addresses. The option is read-only and exclusive with explicit
selectors or discovery; unknown schemas use generic discovery.
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
Expand All @@ -209,6 +213,8 @@ does not prove complete source delivery, model retention or fresh authority.
执行/结算契约前仍须完整读取。未知 schema、过小引用预算和 warm delta 保留既有路线,
关闭原生传输的输出不变;完整恢复和原 SHA 校验均保留。只读的 `--initial-read` 不与
显式选读或索引混用;续页保留短选项和固定的选读摘要,profile 改变则拒绝混读。
首读可把已经完整选中的同级内容合并为其完整父节点,减少地址重复;任何未选中的
可选或未知内容都会阻止合并,选中事实和义务不变。显式选读和索引仍保留原地址。
首读也给出现有验收材料地址和当前结算义务。仅明确 `required=false` 的附件延后完整
说明与模板,使用前须读原附件;未知计划保留全文。首读分页不重复正文中的指针,
选读摘要仍阻止将它记作完整输入。
Expand Down
47 changes: 43 additions & 4 deletions loopx/control_plane/quota/context_selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,50 @@ import {requireJsonObject} from "../runtime_decode.ts";
import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts";
import type {JsonObject} from "../effect_program.ts";

/** Initial navigation may share a complete parent instead of repeating every
* child address. The selected facts stay exact; unselected children prevent
* sharing. Explicit selectors and discovery keep their original addresses. */
function coalescedInitialPointers(context: JsonObject, pointers: string[]): string[] {
if (pointers.length < 2) return pointers;
const selected = new Set(pointers), positions = new Map<string, number>(), visited = new Set<string>();
pointers.forEach((pointer, position) => {
if (!positions.has(pointer)) positions.set(pointer, position);
for (let index = 0; index < pointer.length; index++) {
if (pointer[index] !== "/") continue;
const parent = pointer.slice(0, index);
if (!positions.has(parent)) positions.set(parent, position);
}
});
const visit = (value: unknown, pointer: string): {complete: boolean; pointers: string[]} => {
if (selected.has(pointer)) {
visited.add(pointer);
return {complete: true, pointers: [pointer]};
}
if (!positions.has(pointer) || value === null || typeof value !== "object")
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 single child needs no wrapper; grouping it can increase output bytes.
return {complete, pointers: complete && fields.length > 1 ? [pointer] : fields};
};
const result = visit(context, "").pointers;
// Keep malformed/missing or overlapping addresses for the ordinary validator;
// an optimization must not turn a rejected read into a successful subset.
// A merged parent takes its first selected child's navigation position.
return visited.size === selected.size
? result.sort((left, right) => positions.get(left)! - positions.get(right)!) : pointers;
}

export function capturedContextSelection(request: JsonObject, initialPointers: string[] = []): JsonObject {
const context = requireJsonObject(request.context, "captured context");
if (request.initial_read !== undefined && typeof request.initial_read !== "boolean")
throw new EffectRuntimeRequestError("initial-read must be boolean");
if (request.initial_read === true && (request.select !== undefined || request.list_fields === true || request.list_depth !== undefined))
throw new EffectRuntimeRequestError("initial-read cannot be combined with select or list");
const selectors = request.initial_read === true ? initialPointers : request.select;
const initial = request.initial_read === true ? coalescedInitialPointers(context, initialPointers) : [];
const selectors = request.initial_read === true ? initial : request.select;
if (request.initial_read === true && !(selectors as string[]).length)
throw new EffectRuntimeRequestError("initial-read is unavailable for this capture schema; use generic discovery");
if (!Array.isArray(selectors) || !selectors.length || selectors.some(value => typeof value !== "string"))
Expand Down Expand Up @@ -64,7 +101,9 @@ export function capturedContextSelection(request: JsonObject, initialPointers: s
}
return {pointer, path};
});
const initial = request.list_fields === true && pointers.length === 1 && pointers[0] === ""
? initialPointers : [];
return {selections, ...(initial.length ? {initial_read_pointers: initial} : {})};
// Discovery follows the validated, deduplicated selection. Repeating the root
// cannot drop its navigation; ordinary explicit reads need no initial plan.
const navigation = request.list_fields === true && pointers.length === 1 && pointers[0] === ""
? coalescedInitialPointers(context, initialPointers) : [];
return {selections, ...(navigation.length ? {initial_read_pointers: navigation} : {})};
}
16 changes: 16 additions & 0 deletions tests/control_plane/codex_checkpoint_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


def selected_context_values(content):
"""Address selected facts independently of complete parent packaging."""
values = {}

def visit(pointer, value):
values[pointer] = value
if isinstance(value, (dict, list)):
children = value.items() if isinstance(value, dict) else enumerate(value)
for key, child in children:
visit(pointer + "/" + str(key).replace("~", "~0").replace("/", "~1"), child)

for item in json.loads(content)["selections"]:
visit(item["pointer"], item["value"])
return values


def delivered_context(request, digest):
"""Independent byte-map oracle over actual native model input."""
import hashlib
Expand Down
69 changes: 68 additions & 1 deletion tests/control_plane/test_context_first_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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
from tests.control_plane.codex_checkpoint_fixture import selected_context_values


@pytest.fixture
Expand Down Expand Up @@ -68,7 +69,73 @@ def read_selection(command):
offset=0, page_bytes=64000, max_page_output_bytes=64000)
page = read_context_capture(args)
assert page["ok"] and page["eof"]
return page, {item["pointer"]: item["value"] for item in json.loads(page["content"])["selections"]}
return page, selected_context_values(page["content"])


def test_initial_read_coalesces_whole_contract_with_exact_values_and_unknown_obligations(tmp_path, decision):
cli = decision["interaction_contract"]["cli_channel"]
cli["settlement_plan"]["ordered_steps"] = [{
"kind": "durable_writeback", "precondition": "Keep the original identity and final stop.",
"unknown_obligation": {"allowed": False, "empty": [], "a/b~c": "Preserve this selected constraint."},
"measurement": [2**60 + 1, 1.0, -0.0],
}]
view = native_context_capture_view(decision, tmp_path)
page, values = read_selection(view["first_read_command"])
assert values["/interaction_contract/cli_channel"] == cli
pointers = [item["pointer"] for item in json.loads(page["content"])["selections"]]
assert "/interaction_contract/cli_channel" in pointers
assert not any(pointer.startswith("/interaction_contract/cli_channel/") for pointer in pointers)
assert str(2**60 + 1) in page["content"] and "1.0,-0.0" in page["content"]
# The complete source, including unrelated deferred evidence, is unchanged.
raw = Path(view["recovery"]["path"]).read_bytes()
assert json.loads(raw) == decision
assert hashlib.sha256(raw).hexdigest() == page["sha256"]
assert "older-evidence-" not in page["content"]


@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 (
AGENT_ID, GOAL_ID, TODO_ID, _heartbeat_receipt_count, _initialize_fixture_authority,
_run_cli, _spend_run_count, _write_fixture,
)
from tests.control_plane.canonical_authority_fixture import isolate_sqlite_runtime
from loopx.control_plane.turn_driver.decision_capture import read_context_read_reference

isolate_sqlite_runtime(tmp_path, monkeypatch)
monkeypatch.delenv("CODEX_THREAD_ID", raising=False)
project, runtime, registry = _write_fixture(tmp_path)
_initialize_fixture_authority(project, runtime, provider=provider, handoff_mode="hard_lease")
state = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md"
original_state = state.read_bytes()
turn_id = "native-first-read"
code, view = _run_cli(registry, runtime, "quota", "should-run", "--codex-app",
"--context-delivery", "on", "--goal-id", GOAL_ID, "--agent-id", AGENT_ID,
"--todo-id", TODO_ID, "--turn-instance-id", turn_id, cwd=project)
assert code == 0 and view["mode"] == "full", view
_, reference = read_context_read_reference(view["recovery"]["reference_file"])
raw = Path(reference["path"]).read_bytes()
source = json.loads(raw)
words = shlex.split(view["first_read_command"])
for option in ("--page-bytes", "--max-page-output-bytes"):
words[words.index(option) + 1] = "4500"
content = []
for _ in range(20):
code, page = _run_cli(registry, runtime, *words[words.index("quota"):], cwd=project)
assert code == 0 and page["ok"], page
assert len((json.dumps(page, ensure_ascii=False, separators=(",", ":")) + "\n").encode()) <= 4500
assert capture_page_range(page, raw=raw, digest=reference["sha256"]) is None
content.append(page["content"])
if page["eof"]:
break
words = shlex.split(page["next_command"])
assert page["eof"] and len(content) > 1
values = selected_context_values("".join(content))
assert values["/interaction_contract/cli_channel"] == source["interaction_contract"]["cli_channel"]
assert values["/heartbeat_receipt"] == source["heartbeat_receipt"]
assert values["/selected_todo"] == source["selected_todo"]
assert _heartbeat_receipt_count(runtime, turn_id) == 1 and _spend_run_count(runtime) == 0
assert Path(reference["path"]).read_bytes() == raw and state.read_bytes() == original_state


def test_initial_read_keeps_current_constraints_whole_and_history_reachable(tmp_path, decision):
Expand Down
78 changes: 77 additions & 1 deletion tests/control_plane_ts/context_selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import {test} from "node:test";
import {evaluateTurnEnvelope} from "../../loopx/control_plane/quota/turn_envelope.ts";
import {effectRuntimeErrorPayload} from "../../loopx/control_plane/effect_runtime_errors.ts";
import {capturedContextSelection} from "../../loopx/control_plane/quota/context_selection.ts";

const select = (context: object, pointers: unknown) => evaluateTurnEnvelope({operation: "context_select", context, select: pointers});

Expand Down Expand Up @@ -111,7 +112,7 @@ test("full decision discovery points to current controls and whole source obliga
for (const pointer of ["/execution_obligation", "/goal_boundary", "/selected_todo",
"/interaction_contract/agent_channel/required_reads", "/interaction_contract/cli_channel/spend_allowed_now",
"/interaction_contract/agent_channel/work_context/sources/0/content/text"])
assert.ok(pointers.includes(pointer), pointer);
assert.ok(pointers.some(parent => pointer === parent || pointer.startsWith(parent + "/")), pointer);
assert.ok(!pointers.some(pointer => /writeback_results|full_criteria/.test(pointer)));
assert.doesNotThrow(() => select(context, pointers));
// Discovery is not a claim that omitted criteria were read or are optional.
Expand All @@ -122,6 +123,81 @@ test("full decision discovery points to current controls and whole source obliga
assert.deepEqual(evaluateTurnEnvelope({operation: "context_select", context, initial_read: true}), select(context, pointers));
});

// Compare the selected facts independently of their parent/child packaging.
// Empty containers are facts too; neither omission nor accidental expansion is equivalent.
function selectedFacts(context: unknown, selections: {path: (string | number)[]}[]): string[] {
const facts = new Set<string>();
const visit = (value: unknown, path: (string | number)[]) => {
if (value === null || typeof value !== "object" || !Object.keys(value).length) {
facts.add(JSON.stringify([path, value]));
return;
}
for (const [key, child] of Object.entries(value))
visit(child, [...path, Array.isArray(value) ? Number(key) : key]);
};
for (const selection of selections) {
let value = context;
for (const key of selection.path) value = (value as Record<string | number, unknown>)[key];
visit(value, selection.path);
}
return [...facts].sort();
}

test("initial navigation coalesces complete branches without changing selected facts or explicit readers", () => {
const context = {facts: [{"a/b": "Keep the final stop.", "~": null}, {allowed: false, evidence: []}],
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 before = structuredClone(context);
const explicit = select(context, pointers);
const initial = capturedContextSelection({context, initial_read: true}, pointers);
assert.deepEqual(initial, select(context, ["/facts", "/single/only"]));
assert.deepEqual(selectedFacts(context, initial.selections as {path: (string | number)[]}[]),
selectedFacts(context, explicit.selections as {path: (string | number)[]}[]));
assert.deepEqual((explicit.selections as {pointer: string}[]).map(row => row.pointer), [...pointers].sort());
const discovery = capturedContextSelection({context, select: [""], list_fields: true}, pointers);
assert.deepEqual(discovery.initial_read_pointers, ["/single/only", "/facts"]);
assert.deepEqual(discovery.selections, [{pointer: "", path: [], children: [
{pointer: "/facts", path: ["facts"]}, {pointer: "/single", path: ["single"]}, {pointer: "/unread", path: ["unread"]},
]}]);
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"); }});
for (const pointers of [["/facts"], ["/facts", "/facts"]]) {
assert.deepEqual(capturedContextSelection({context, select: pointers}, unused), select(context, pointers));
assert.deepEqual(capturedContextSelection({context, select: pointers, list_fields: true}, unused),
evaluateTurnEnvelope({operation: "context_select", context, select: pointers, list_fields: true}));
}
});

test("duplicate root selectors retain the same discovery navigation", () => {
const context = {facts: {allowed: false, stop: "Preserve the final boundary."}, unread: "optional material"};
const initial = ["/facts/allowed", "/facts/stop"];
const single = capturedContextSelection({context, select: [""], list_fields: true}, initial);
const repeated = capturedContextSelection({context, select: ["", ""], list_fields: true}, initial);
assert.deepEqual(single.initial_read_pointers, ["/facts"]);
assert.deepEqual(repeated, single);
assert.throws(() => capturedContextSelection({context, select: ["", "", "/missing"], list_fields: true}, initial),
/does not exist/);
});

test("partial coverage never expands unknown or optional facts through a parent", () => {
const context = {controls: {allowed: false, instruction: "Preserve this constraint.", unknown: "not selected"},
attachment: {required: false, command: "read the attachment before use", recipe: "deferred recipe"},
required: {unknown_obligation: "This selected unknown obligation stays whole."}};
const pointers = ["/controls/allowed", "/controls/instruction", "/attachment/required", "/attachment/command", "/required"];
const initial = capturedContextSelection({context, initial_read: true}, pointers);
assert.deepEqual(initial, select(context, pointers));
const facts = selectedFacts(context, initial.selections as {path: (string | number)[]}[]);
assert.ok(facts.some(value => value.includes("selected unknown obligation")));
assert.ok(!facts.some(value => /not selected|deferred recipe/.test(value)));
assert.throws(() => capturedContextSelection({context, initial_read: true}, [...pointers, "/missing"]), /does not exist/);
});

test("unknown decision schemas and ordinary selections keep generic discovery", () => {
for (const context of [{value: true}, {interaction_contract: {schema_version: "future"}}]) {
const result = evaluateTurnEnvelope({operation: "context_select", context, select: [""], list_fields: true});
Expand Down
4 changes: 3 additions & 1 deletion tests/test_delegation_turn_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ def test_actual_cli_guards_and_host_envelopes_preserve_the_agenda(tmp_path, monk
assert words[0] == "loopx"
code, page = run_json_cli_result(*words[1:], registry_path=registry, runtime_root=root)
assert code == 0 and page["ok"] and page["eof"], page
values = {row["pointer"]: row["value"] for row in json.loads(page["content"])["selections"]}
from tests.control_plane.codex_checkpoint_fixture import selected_context_values

values = selected_context_values(page["content"])
pointer = next(key for key in values if key.endswith("/content/unfinished_delegations"))
content = {"unfinished_delegations": values[pointer],
"read_complete": values[pointer.removesuffix("unfinished_delegations") + "read_complete"]}
Expand Down
Loading