diff --git a/docs/reference/required-work-context.md b/docs/reference/required-work-context.md index a8477c87f9..cf07e55b8a 100644 --- a/docs/reference/required-work-context.md +++ b/docs/reference/required-work-context.md @@ -194,11 +194,15 @@ input; grouping saves repeated wrappers rather than omitting controls. The codec compares this encoding with ordinary edits and full replacement before choosing. A reader that does not recognize the edit recovers the full capture. -Eligible reusable strings may also use `from`, an array of object keys or exact -array indices in the immutable base capture. This avoids resending identical -authoring when task binding or source ordering moves it to another field. The -reference copies the complete exact string, never a similar string or a value -from the current patch. Current and unknown controls remain inline. Missing, +Eligible reusable strings, arrays and objects may also use `from`, an array of +object keys or exact array indices in the immutable base capture. This avoids +resending identical authoring and evidence records when task binding or source +ordering moves them to another field. The reference copies the complete exact +value, never a similar value or one from the current patch. A subtree containing +any field required as current input cannot use a whole-subtree reference. The +codec chooses a reference only when smaller than its literal or recursive edit. +Current and unknown controls remain inline. Older string-only readers recover +the current full capture for an unsupported structured reference. Missing, malformed or incompatible references recover the same full current capture; the reconstructed context must still match its complete digest. Cold delivery, session invalidation and authority remain unchanged. This saves transport only; @@ -449,6 +453,12 @@ TurnEnvelope。在基线连续性成立的口径内,两入口仍逐轮发送 ` `removed`;数组的 `items` 与 `values` 使用精确索引,`length` 调整长度。 字段仍在本轮输入中,只减少重复 wrapper;codec 比较普通编辑、合并字段 与完整替换后选择,无法识别该编辑的读者恢复完整 capture。 +可复用的字符串、数组和对象还可用 `from` 引用不可变基线中的精确键或数组索引, +避免绑定任务或来源重排后重传相同的指导与证据记录。它复制完整相同的值,不读取 +当前 patch,也不模糊匹配。含有必须逐轮发送字段的子树不能整体引用;引用只有比 +完整值及递归编辑更小时才采用。当前和未知控制仍完整发送;只支持字符串引用的 +旧读者遇到结构化引用时恢复当前完整 capture。引用缺失、损坏或不兼容仍按同一 +捕获恢复,完整重建摘要须匹配。冷启动、连续性失效、权限与结算语义保持。 这些只改变既有选项内的展示,不新增配置或准入。缺少可用连续性依据、epoch 改变、patch 损坏 或读者不兼容时,自动恢复同一原始 full capture,不重跑 guard、不问询、不扣额, 也不授予执行权限。传输减少本身不证明实际模型费用或结果改善。 diff --git a/loopx/control_plane/quota/context_delivery.ts b/loopx/control_plane/quota/context_delivery.ts index ccd0f85e33..38a0068586 100644 --- a/loopx/control_plane/quota/context_delivery.ts +++ b/loopx/control_plane/quota/context_delivery.ts @@ -334,16 +334,26 @@ function changeTree(before: unknown, after: unknown, forced: Path[]): Delta | nu const exact = new Set(forced.map(pathKey)), ancestors = new Set(); for (const path of forced) for (let i = 0; i < path.length; i++) ancestors.add(pathKey(path.slice(0, i))); // Binding or source reordering can relocate already-read authoring. Index - // exact strings once; a reusable target may copy them from the immutable - // base. Forced current controls still take precedence over this index. + // exact strings and structured subtrees from the immutable base. A target + // containing any forced-current descendant cannot use a subtree reference. const strings = new Map(); + type Candidate = {reference: {from: Path}; value: unknown}; + const subtrees = new Map}>(); const index = (value: unknown, path: Path) => { if (typeof value === "string") { const reference = {from: path}, previous = strings.get(value); if (bytes(reference) < bytes({value}) && (!previous || bytes(reference) < bytes(previous))) strings.set(value, reference); - } else if (Array.isArray(value)) value.forEach((v, i) => index(v, [...path, i])); - else if (jsonObject(value)) Object.entries(value as JsonObject).forEach(([k, v]) => index(v, [...path, k])); + } else if (Array.isArray(value) || jsonObject(value)) { + const reference = {from: path}, valueSize = size(value); + if (bytes(reference) < size({value})) { + const bucket = subtrees.get(valueSize) ?? {candidates: []}; + bucket.candidates.push({reference, value}); + subtrees.set(valueSize, bucket); + } + if (Array.isArray(value)) value.forEach((v, i) => index(v, [...path, i])); + else Object.entries(value as JsonObject).forEach(([k, v]) => index(v, [...path, k])); + } }; index(before, []); const visit = (a: unknown, b: unknown, path: Path): Delta | null => { @@ -351,6 +361,23 @@ function changeTree(before: unknown, after: unknown, forced: Path[]): Delta | nu if (exact.has(key)) return {value: clone(b)}; if (!ancestors.has(key) && equal(a, b)) return null; if (typeof b === "string" && strings.has(b)) return clone(strings.get(b)!); + let reference: {from: Path} | undefined; + if (!ancestors.has(key) && (Array.isArray(b) || jsonObject(b))) { + // Most current controls cannot reuse a subtree, and unchanged siblings + // already returned above. Hash only size buckets actually needed by a + // remaining candidate; avoid hashing the complete baseline eagerly. + const bucket = subtrees.get(size(b)); + if (bucket && !bucket.indexed) { + bucket.indexed = new Map(); + for (const candidate of bucket.candidates) { + const hash = digest(candidate.value), previous = bucket.indexed.get(hash); + if (!previous || bytes(candidate.reference) < bytes(previous.reference)) bucket.indexed.set(hash, candidate); + } + } + const reusable = bucket?.indexed?.get(digest(b)); + // Size and hash lookup are indices: exact equality remains the oracle. + if (reusable && equal(reusable.value, b)) reference = reusable.reference; + } let delta: Delta; if ((jsonObject(a) || a === undefined) && jsonObject(b)) { const old = (a ?? {}) as JsonObject, next = b as JsonObject; @@ -369,32 +396,34 @@ function changeTree(before: unknown, after: unknown, forced: Path[]): Delta | nu if (child) items.push([String(i), child]); }); delta = groupCurrentValues({items: Object.fromEntries(items), ...(old?.length !== b.length ? {length: b.length} : {})}, size); - } else return {value: clone(b)}; + } else delta = {value: clone(b)}; // Sizing does not publish a value. Copy only a replacement that is chosen, // rather than every unchanged subtree and every discarded ancestor value. - return size(delta) < size({value: b}) ? delta : {value: clone(b)}; + const result = size(delta) < size({value: b}) ? delta : {value: clone(b)}; + return reference && size(reference) < size(result) ? clone(reference) : result; }; return visit(before, after, []); } /** Array indices address the exact hashed baseline, not inferred entity ids. * Reorders, duplicate values and length changes are lossless structural edits. - * A relocated string may reference the exact immutable base. No timestamp, + * A relocated value may reference the exact immutable base. No timestamp, * revision normalization, fuzzy identity or prose diff is involved. */ function applyTree(before: unknown, delta: Delta | null, baseline: unknown = before): unknown { if (delta === null) return clone(before); if ("value" in delta) return clone(delta.value); if ("from" in delta) { - if (Object.keys(delta).length !== 1 || !Array.isArray(delta.from)) throw new Error("invalid string reference"); + if (Object.keys(delta).length !== 1 || !Array.isArray(delta.from)) throw new Error("invalid baseline reference"); let value = baseline; for (const key of delta.from) { if (!(typeof key === "string" || typeof key === "number" && Number.isSafeInteger(key) && key >= 0) || value === null || typeof value !== "object" || !Object.hasOwn(value, key)) - throw new Error("string reference is not an own baseline path"); + throw new Error("reference is not an own baseline path"); value = (value as Record)[key]; } - if (typeof value !== "string") throw new Error("string reference has no exact baseline string"); - return value; + if (typeof value !== "string" && !Array.isArray(value) && !jsonObject(value)) + throw new Error("reference has no exact baseline string or subtree"); + return clone(value); } if ("items" in delta) { if (before !== undefined && !Array.isArray(before)) throw new Error("array baseline missing"); @@ -469,7 +498,7 @@ export function prepareContextDelivery(request: JsonObject, legacyQuotaFields: r + "Do not also fetch the full projection just to materialize JSON or replace missing Code Mode variables; " + "tool-process memory is separate from retained model context. If required baseline content is unavailable " + "or continuity is uncertain, recover this capture in full before dependent work. " : "") - + "A value replaces its subtree; from copies the exact string at that path in the base capture, never in current changes. Values assigns complete fields (exact indices with items); changed/items recurse, removed deletes object keys, length sets array length. Lost context triggers automatic full recovery from this capture, never an owner question."}; + + "A value replaces its subtree; from copies the exact string, array or object at that path in the base capture, never in current changes. Values assigns complete fields (exact indices with items); changed/items recurse, removed deletes object keys, length sets array length. Lost context triggers automatic full recovery from this capture, never an owner question."}; if (bytes(delta) >= bytes(full("delta_not_smaller"))) return full("delta_not_smaller"); if (!equal(applyTree(prior, changes), context)) return full("reconstruction_failed"); // Native consumers page the wire before they can use its baseline. Deliver diff --git a/tests/control_plane/test_codex_context_boundary.py b/tests/control_plane/test_codex_context_boundary.py index 51ac9d96ac..fdd3ea48f6 100644 --- a/tests/control_plane/test_codex_context_boundary.py +++ b/tests/control_plane/test_codex_context_boundary.py @@ -232,8 +232,16 @@ def test_native_shared_delta_with_small_compact_recovery_reference(tmp_path, hos scope = dict(goal_id="fixture", agent_id="agent", session_id="synthetic-session", context_epoch="before-compact") source = {"complete": True, "sources": [{"source": "selected_todo", "content": { "todo": {"todo_id": "todo_fixture", "text": SOURCE, "note": "Stop before publishing.", "status": "open"}}}]} + # A real reader must receive relocated structured provenance as well as + # string edits. Current review conclusions remain literal on both surfaces. + source["sources"].append({"source": "goal_acceptance", "content": { + "goal_acceptance_contract": {"outcome_requirements": {"revision": 1, "outcomes": [{ + "id": "outcome", "requirements": [{"id": "requirement", "status": "unverified", + "agent_review": {"conclusion": "uncertain", "evidence_refs": [ + f"artifact:validation-{i:03d}" for i in range(60)]}}]}]}}}}) target_source = json.loads(json.dumps(source)) target_source["sources"][0]["content"]["todo"]["note"] = "Stop before publishing or deploying." + target_source["sources"].reverse() def projection(context): if surface == "quota_packet": return {"goal_id": "fixture", "agent_id": "agent", "should_run": True, @@ -245,6 +253,16 @@ def projection(context): delta = prepare_context_delivery(target_source, scope=scope, capture_id="capture-b", surface=surface, baseline=base, retained={"complete": True, "target": base["target"]}) assert delta["mode"] == "delta" + def structured_reference(node): + if not isinstance(node, dict): + return False + if "from" in node: + value = base["context"] + for key in node["from"]: + value = value[key] + return isinstance(value, (dict, list)) + return any(structured_reference(child) for child in node.get("changed", node.get("items", {})).values()) + assert structured_reference(delta["changes"]) assert restore_context_delivery(delta, baseline=base, current=target)["context"] == target_source files = {} for name, value in (("base", base), ("target", target), ("delta", delta)): diff --git a/tests/control_plane_ts/context_delivery.test.ts b/tests/control_plane_ts/context_delivery.test.ts index 6b7514f406..f662de04cf 100644 --- a/tests/control_plane_ts/context_delivery.test.ts +++ b/tests/control_plane_ts/context_delivery.test.ts @@ -221,6 +221,49 @@ test("new reusable arrays can relocate exact strings without copying current edi assert.ok(wire.length < JSON.stringify(current).length / 2); }); +test("relocated immutable records reuse complete subtrees without hiding fresh descendants", () => { + const records = Array.from({length: 48}, (_, i) => ({id: `r${i}`, + evidence: [`artifact:original-${i}`, `artifact:validation-${i}`], + details: {accepted: false, attempts: [i, i + 1]}})); + const original = {registry: {records, object: {records}}, current_control: {records}}; + const current = {registry: {moved: {records, object: {records}}}, current_control: {records}}; + const initial = prepareContextDelivery({context: original, scope, capture_id: "before", surface: "quota_packet"}); + const args = {context: current, scope, capture_id: "after", surface: "quota_packet"}; + const delta = prepareContextDelivery({...args, baseline: initial, retained: {complete: true, target: initial.target}}); + assert.equal(delta.mode, "delta"); + // A relocated array of short, typed records must not retransmit every record. + const tree = (delta.changes as JsonObject).changed as JsonObject; + assert.ok(Buffer.byteLength(JSON.stringify(tree.registry)) < 600); + assert.deepEqual(inline(delta, ["current_control"]), current.current_control); + const restored = restoreContextDelivery({delivery: delta, baseline: initial, current: prepareContextDelivery(args)}); + assert.deepEqual(restored.context, current); + ((restored.context as typeof current).registry.moved.records[0].details).accepted = true; + assert.equal(original.registry.records[0].details.accepted, false); + assert.equal(current.registry.moved.records[0].details.accepted, false); + + const changed = structuredClone(current); + changed.registry.moved.records.reverse(); + changed.registry.moved.records[0].details.accepted = true; + changed.registry.moved.records[1].evidence = []; + const edited = {...args, context: changed}; + const next = prepareContextDelivery({...edited, baseline: initial, retained: {complete: true, target: initial.target}}); + assert.deepEqual(restoreContextDelivery({delivery: next, baseline: initial, + current: prepareContextDelivery(edited)}).context, changed); + + // Reuse cannot swallow a known fresh control nested in an otherwise equal + // container. Existing acceptance/current-review assertions cover every surface. + const horizon = {core_goal: {objective: long, acceptance_contract: {records}}}; + const first = prepareContextDelivery({context: {planning_horizon: horizon}, scope, + capture_id: "horizon-before", surface: "quota_packet"}); + const request = {context: {planning_horizon: structuredClone(horizon)}, scope, + capture_id: "horizon-after", surface: "quota_packet"}; + const second = prepareContextDelivery({...request, baseline: first, retained: {complete: true, target: first.target}}); + assert.equal(second.mode, "delta"); + assert.equal(inline(second, ["planning_horizon", "core_goal", "objective"]), long); + assert.deepEqual(restoreContextDelivery({delivery: second, baseline: first, + current: prepareContextDelivery(request)}).context, request.context); +}); + test("exact-base source and requirement reorders preserve current positions", () => { const initial = context(); const outcomes = ((source(initial, 2).goal_acceptance_contract as JsonObject).outcome_requirements as JsonObject).outcomes as JsonObject[];