From 7c1241b37f3728beb23f91d726fe4bc3fd4ffce3 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 25 Oct 2026 03:39:05 +0800 Subject: [PATCH 1/7] perf(quota): align context reuse and compact current literal edits Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- docs/reference/required-work-context.md | 47 +++++++ .../capabilities/explore_turn_context.ts | 13 +- loopx/control_plane/capability_hooks.ts | 4 +- loopx/control_plane/quota/context_delivery.ts | 62 +++++++-- .../control_plane_ts/context_delivery.test.ts | 120 +++++++++++++++++- 5 files changed, 228 insertions(+), 18 deletions(-) diff --git a/docs/reference/required-work-context.md b/docs/reference/required-work-context.md index 112cee342d..08be6f8f36 100644 --- a/docs/reference/required-work-context.md +++ b/docs/reference/required-work-context.md @@ -117,6 +117,37 @@ upgrades installed automations nor proves model adoption or token/latency gains. To roll back, install the prior revision and regenerate prompts; changing a transport flag does not restore the old exact Todo schema. +## Opt-in native reuse + +The existing [native context delivery option](../heartbeat-automation-prompt.md#opt-in-native-context-delivery) +uses one TypeScript codec for ordinary quota and TurnEnvelope. Within a confirmed +baseline, both representations repeat `planning_horizon.core_goal`'s original +objective, provenance and availability, including a changed or missing objective. +The complete `acceptance_contract` and registered planning trees use exact +reuse, subtree changes or a complete refresh; the acceptance tree is not treated +as the original Goal sentence. Unknown horizon fields remain current input. + +The same codec retains complete static closure guidance in quota channels and +the Turn contract capsule. Current closure decisions, conditions, identities, +commands and unknown controls remain current input. Explore's registered owner +also permits exact reuse of branch evidence audits and its evidence-link template; +branch identity, selection, status, resume facts and orchestration gates remain +current. Reordering, refutation, removed evidence and changed guidance reconstruct +the exact current source; no timestamp, revision or text is normalized away. + +Object edits may group complete current fields in `values`, alongside recursive +`changed` edits and `removed` keys. Array edits use exact indices in `items` and +`values`, with `length` for resizing. These literals are present in the current +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. + +These are presentation rules within the existing default-off option, not a new +configuration or admission policy. Missing retention, a changed epoch, a damaged +patch or an incompatible reader automatically recovers the same original full +capture. Recovery never reruns the guard, asks the owner, spends quota or grants +execution. Wire reduction alone does not establish actual model costs or outcomes. + ## 中文 普通 Heartbeat/quota 默认在 `agent_channel.work_context` 返回完整选定 Todo、 @@ -160,3 +191,19 @@ Goal adapter 仍完整读取文件、校验 UTF-8,并以全部字节计算修 安装后重新生成旧展开式 heartbeat 提示;此 PR 不热更新 runtime/automation, 也不证明模型已采用或获得质量收益。回滚需恢复旧版本并重新生成提示。 + +既有默认关闭的原生 context delivery 由同一 TypeScript codec 处理普通 quota 与 +TurnEnvelope。确认基线保留后,两入口仍逐轮发送 `planning_horizon.core_goal` 的 +原始目标、来源和可用性,包括目标改变或缺失。完整 `acceptance_contract` 与已登记 +规划树按精确复用、子树变化或全量交付;累计验收树不等于原始目标句。未知 horizon +字段仍作为本轮输入。quota 通道与 Turn 契约摘要复用完整静态收口指导,当前收口判断、 +条件、身份、命令与未知控制仍逐轮发送。Explore owner 登记分支证据审计与证据关联 +模板的精确复用;分支身份、选择、状态、恢复事实与编排门仍是当前输入。重排、反证、 +移除与指导变更完整重建当前来源,不规范化时间戳、修订或正文。 +对象编辑可以在 `values` 中合并携带本轮完整字段,同时保留递归 `changed` 与删除 +`removed`;数组的 `items` 与 `values` 使用精确索引,`length` 调整长度。 +字段仍在本轮输入中,只减少重复 wrapper;codec 比较普通编辑、合并字段 +与完整替换后选择,无法识别该编辑的读者恢复完整 capture。 +这些只改变既有选项内的展示,不新增配置或准入。保留不明、epoch 改变、patch 损坏 +或读者不兼容时,自动恢复同一原始 full capture,不重跑 guard、不问询、不扣额, +也不授予执行权限。传输减少本身不证明实际模型费用或结果改善。 diff --git a/loopx/control_plane/capabilities/explore_turn_context.ts b/loopx/control_plane/capabilities/explore_turn_context.ts index 4d864d2ebf..19f5fbc145 100644 --- a/loopx/control_plane/capabilities/explore_turn_context.ts +++ b/loopx/control_plane/capabilities/explore_turn_context.ts @@ -14,7 +14,7 @@ import {projectExploreMeasurementFeedback} from "./explore_measurements.ts"; * selection, eligibility, source revisions and pagination stay outside this * split. Exact array edits retain every change, including its status, * provenance and applicability; no timestamp or revision is normalized away. */ -export function exploreReusableContextPaths(): string[][] { +export function exploreReusableContextPaths(content: JsonObject = {}): (string | number)[][] { return [ ...["recent_nodes", "recent_findings", "writeback_results", "measurement_feedback", "path_delta_attachment_template", "linked_question_attachment_template", @@ -23,7 +23,16 @@ export function exploreReusableContextPaths(): string[][] { "record_node_template", "record_finding_template", "guidance", "result_writeback_option", "result_writeback_inline_option", "result_writeback_inline_field", "result_attachment_schema", "path_delta_attachment_schema"].map(key => ["graph", key]), - ["harness", "guidance"], + ["harness", "guidance"], ["harness", "link_evidence_template"], + // The branch's identity, selection, status and resume facts stay fresh. + // Its evidence audit is a read model, not an eligibility or execution grant. + ...["selected_branches", "comparison_candidates", "rejected_candidates", "frontier"].flatMap(lane => { + const branches = (content.harness as JsonObject | undefined)?.[lane]; + return Array.isArray(branches) ? branches.flatMap((branch, index) => + branch !== null && typeof branch === "object" && !Array.isArray(branch) + && typeof branch.todo_id === "string" && branch.todo_id + ? [["harness", lane, index, "typed_evidence_audit"]] : []) : []; + }), ]; } diff --git a/loopx/control_plane/capability_hooks.ts b/loopx/control_plane/capability_hooks.ts index 9fb75e3457..7f2fe2fdfa 100644 --- a/loopx/control_plane/capability_hooks.ts +++ b/loopx/control_plane/capability_hooks.ts @@ -25,10 +25,10 @@ import {exploreReusableContextPaths} from "./capabilities/explore_turn_context.t /** The registered owner selects reusable read-model subtrees. Unknown hooks * remain complete; payloads cannot nominate their own execution fields. */ -export function reusableTurnContextPaths(source: JsonObject): string[][] { +export function reusableTurnContextPaths(source: JsonObject): (string | number)[][] { return source.source === "turn_start_capability_hook" && source.hook_id === "explore.turn_context" && source.capability_id === "explore" && source.kind === "explore_turn_context" - ? exploreReusableContextPaths() : []; + ? exploreReusableContextPaths(source.content as JsonObject | undefined) : []; } export const CAPABILITY_HOOK_REGISTRATION_SCHEMA_VERSION = diff --git a/loopx/control_plane/quota/context_delivery.ts b/loopx/control_plane/quota/context_delivery.ts index 6ee18026bc..0b279015ec 100644 --- a/loopx/control_plane/quota/context_delivery.ts +++ b/loopx/control_plane/quota/context_delivery.ts @@ -121,8 +121,10 @@ function splitContext(context: JsonObject): Split { sourceIds.add(id); for (const path of paths) { let value: unknown = content; - for (const key of path.slice(0, -1)) value = jsonObject(value)?.[key]; - if (jsonObject(value)) add(JSON.stringify([...identity, ...path]), [...prefix, ...path], true); + for (const key of path.slice(0, -1)) value = Array.isArray(value) + ? value[Number(key)] : jsonObject(value)?.[key]; + if (jsonObject(value) || Array.isArray(value)) + add(JSON.stringify([...identity, ...path]), [...prefix, ...path], true); } } } @@ -133,8 +135,8 @@ function splitContext(context: JsonObject): Split { * Keep one exact codec for quota and Turn; source-only callers use the same codec. */ const SURFACES = ["work_context", "quota_packet", "turn_envelope"] as const; type Surface = typeof SURFACES[number]; -type Delta = {value: unknown} | {changed: Record; removed?: string[]} - | {items: Record; length?: number}; +type Delta = {value: unknown} | {changed?: Record; values?: Record; removed?: string[]} + | {items: Record; values?: Record; length?: number}; const pathKey = (path: Path): string => JSON.stringify(path); // These are existing quota projections. Unknown root fields stay fresh. Within @@ -175,10 +177,11 @@ function freshPaths(context: JsonObject, surface: Surface, legacyQuotaFields: re const audit = jsonObject(value); if (!audit) { paths.push(path); return; } for (const [key, item] of Object.entries(audit)) { - if (key === "required_before_closeout") continue; // complete binding instructions in the exact patch + if (["required_before_closeout", "authoritative_evidence_kinds", "not_satisfied_by"].includes(key)) continue; if (key !== "vision_gap_judge" || !jsonObject(item)) { paths.push([...path, key]); continue; } for (const field of Object.keys(item as JsonObject)) - if (!["agent_judge_instruction", "evidence_read_instruction", "registry_read_instruction", "done_only_when", "continue_when"].includes(field)) + if (!["agent_judge_instruction", "evidence_read_instruction", "registry_read_instruction", + "external_research_instruction", "research_writeback_required_when_used", "done_only_when", "continue_when"].includes(field)) paths.push([...path, key, field]); } }; @@ -202,10 +205,30 @@ function freshPaths(context: JsonObject, surface: Surface, legacyQuotaFields: re }); } }; + const horizon = (value: unknown, path: Path) => { + const object = jsonObject(value); + if (!object) { paths.push(path); return; } + for (const [field, item] of Object.entries(object)) { + if (field === "core_goal" && jsonObject(item)) { + // Repeat the original intent, provenance and availability. The complete + // cumulative acceptance tree may be large; it is not the Goal sentence. + for (const key of Object.keys(item as JsonObject)) + if (key !== "acceptance_contract") paths.push([...path, field, key]); + } else if (!["work_items", "relations", "acceptance_gaps", "attention_todo_ids", + "completeness", "detail_refs", "detail_refs_ref", "selection_contract"].includes(field)) paths.push([...path, field]); + } + }; if (surface === "work_context") work(context, []); else if (surface === "turn_envelope") { for (const [key, value] of Object.entries(context)) { if (key === "work_context") work(value, [key]); + else if ((key === "action" || key === "contract_capsule") && jsonObject(value)) { + for (const [field, item] of Object.entries(value as JsonObject)) { + if (key === "action" && field === "planning_horizon") horizon(item, [key, field]); + else if (key === "contract_capsule" && field === "vision_continuation_audit") vision(item, [key, field]); + else paths.push([key, field]); + } + } else if (key === "writeback") { const writeback = jsonObject(value); if (!writeback) { paths.push([key]); continue; } @@ -217,6 +240,7 @@ function freshPaths(context: JsonObject, surface: Surface, legacyQuotaFields: re } } else { for (const [key, value] of Object.entries(context)) { + if (key === "planning_horizon") { horizon(value, [key]); continue; } if (key !== "interaction_contract") { if (!REUSABLE_PACKET_FIELDS.has(key) && !legacyQuotaFields.includes(key)) paths.push([key]); continue; @@ -240,6 +264,21 @@ function freshPaths(context: JsonObject, surface: Surface, legacyQuotaFields: re return {paths, valid}; } +/** Group complete CURRENT literals, not baseline references. Keep the cheaper + * ordinary edit when grouping would grow metadata; both containers share this rule. */ +function groupCurrentValues(delta: Exclude): Delta { + const children = Object.entries("items" in delta ? delta.items : delta.changed ?? {}); + const values = children.filter(([, child]) => "value" in child); + if (values.length < 2) return delta; + const nested = Object.fromEntries(children.filter(([, child]) => !("value" in child))); + const grouped: Exclude = { ...delta, + ...("items" in delta ? {items: nested} : {changed: nested}), + values: Object.fromEntries(values.map(([key, child]) => [key, (child as {value: unknown}).value])), + }; + if (!("items" in grouped) && !Object.keys(nested).length) delete grouped.changed; + return bytes(grouped) < bytes(delta) ? grouped : delta; +} + function changeTree(before: unknown, after: unknown, forced: Path[]): Delta | null { 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))); @@ -256,14 +295,14 @@ function changeTree(before: unknown, after: unknown, forced: Path[]): Delta | nu if (child) changed.push([field, child]); } const removed = Object.keys(old).filter(field => !Object.hasOwn(next, field)); - delta = {changed: Object.fromEntries(changed), ...(removed.length ? {removed} : {})}; + delta = groupCurrentValues({changed: Object.fromEntries(changed), ...(removed.length ? {removed} : {})}); } else if (Array.isArray(a) && Array.isArray(b)) { const items: [string, Delta][] = []; b.forEach((value, i) => { const child = i < a.length ? visit(a[i], value, [...path, i]) : {value: clone(value)}; if (child) items.push([String(i), child]); }); - delta = {items: Object.fromEntries(items), ...(a.length !== b.length ? {length: b.length} : {})}; + delta = groupCurrentValues({items: Object.fromEntries(items), ...(a.length !== b.length ? {length: b.length} : {})}); } else return {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. @@ -282,12 +321,15 @@ function applyTree(before: unknown, delta: Delta | null): unknown { if (!Array.isArray(before)) throw new Error("array baseline missing"); const result = clone(before); if (delta.length !== undefined) result.length = delta.length; + for (const [key, value] of Object.entries(delta.values ?? {})) result[Number(key)] = clone(value); for (const [key, item] of Object.entries(delta.items)) result[Number(key)] = applyTree(before[Number(key)], item); return result; } const source = requireJsonObject(before, "object baseline"), result = clone(source); for (const key of delta.removed ?? []) delete result[key]; - for (const [key, item] of Object.entries(delta.changed)) { + for (const [key, value] of Object.entries(delta.values ?? {})) + Object.defineProperty(result, key, {value: clone(value), enumerable: true, writable: true, configurable: true}); + for (const [key, item] of Object.entries(delta.changed ?? {})) { // Avoid prototype setters for JSON property names such as __proto__. Object.defineProperty(result, key, {value: applyTree(Object.hasOwn(source, key) ? source[key] : undefined, item), enumerable: true, writable: true, configurable: true}); @@ -334,7 +376,7 @@ export function prepareContextDelivery(request: JsonObject, legacyQuotaFields: r const changes = changeTree(prior, context, after.paths); const delta: JsonObject = {schema_version: VERSION, mode: "delta", target, base: binding, ancestry: Number(depth) + 1, changes, - instruction: "Current controls are inline even when unchanged. Other fields inherit the confirmed baseline; all requirements remain binding. Changes contain complete values, object changes/removals or exact array-index edits. Lost context triggers automatic full recovery from this capture, never an owner question."}; + instruction: "Current controls are inline even when unchanged. Other fields inherit the confirmed baseline; all requirements remain binding. A value replaces its subtree; 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"); return delta; diff --git a/tests/control_plane_ts/context_delivery.test.ts b/tests/control_plane_ts/context_delivery.test.ts index d6394e157e..b13ae2dd79 100644 --- a/tests/control_plane_ts/context_delivery.test.ts +++ b/tests/control_plane_ts/context_delivery.test.ts @@ -63,6 +63,11 @@ function inline(delivery: JsonObject, path: (string | number)[]): unknown { return value; } if (i === path.length) return undefined; + if (Object.hasOwn((node.values ?? {}) as JsonObject, path[i])) { + let value = (node.values as Record)[path[i]]; + for (const key of path.slice(i + 1)) value = (value as JsonObject)?.[key]; + return value; + } node = ((node.changed ?? node.items) as Record)?.[path[i]]; } } @@ -258,7 +263,7 @@ function packet(): JsonObject { return {goal_id: "goal", agent_id: "agent", should_run: true, selected_todo: {todo_id: "todo_work", claimed_by: "agent"}, goal_boundary: {write_scope: ["src/**"], approval: "pending", stop: "publication"}, unknown_future_control: {allowed: false}, handoff_readiness: {ready: false}, - automation_liveness: {pause_allowed: false}, autonomous_replan_scope: {owner_agent_ids: ["agent"]}, planning_horizon: {items: [{id: "one", body: long}, {id: "two", body: long}]}, + automation_liveness: {pause_allowed: false}, autonomous_replan_scope: {owner_agent_ids: ["agent"]}, planning_horizon: {work_items: [{id: "one", body: long}, {id: "two", body: long}]}, interaction_contract: {mode: "bounded_delivery", user_channel: {notify: "quiet"}, agent_channel: {delivery_allowed: true, must_attempt: true, work_context: context()}, cli_channel: {spend_policy: "after_validation", settlement_plan: {identity: {turn: "turn-a"}, ordered_steps: [ @@ -273,6 +278,111 @@ function packetWarm(value: JsonObject, baseline: JsonObject, surface = "quota_pa retained: {complete: true, target: baseline.target}}); } +test("grouped literals carry current controls without interpreting object data as patch operations", () => { + const value = {registry: {body: long}, first: null, second: {value: 1, changed: {stop: true}}, + values: {items: [false, 0, ""], removed: ["literal-data"]}, last: "current"}; + const baseline = packetFull(value), delta = packetWarm(value, baseline); + assert.equal(delta.mode, "delta"); + const literals = (delta.changes as JsonObject).values as JsonObject; + assert.ok(Object.hasOwn(literals, "first")); + assert.deepEqual(literals.values, value.values); + assert.deepEqual(inline(delta, ["second"]), value.second); + const capture = packetFull(value, "capture-b"); + assert.deepEqual(restoreContextDelivery({delivery: delta, baseline, current: capture}).context, value); + const forged = structuredClone(delta); + delete ((forged.changes as JsonObject).values as JsonObject).first; + assert.equal(restoreContextDelivery({delivery: forged, baseline, current: capture}).mode, "full"); + literals.second = {value: "wire-only"}; + assert.deepEqual(value.second, {value: 1, changed: {stop: true}}); + assert.deepEqual((baseline.context as typeof value).second, value.second); +}); + +test("quota and Turn replay original Goal intent without replaying the complete acceptance tree", () => { + for (const surface of ["quota_packet", "turn_envelope"]) { + const horizon = {schema_version: "quota_planning_horizon_v0", goal_id: "goal", agent_id: "agent", + core_goal: {objective: "Deliver the original Goal; stop before publication.", objective_source: "active_state", + objective_missing: false, read_action: "read-current-goal", acceptance_contract: {requirements: [long]}}, + work_items: [{todo_id: "a", text: long}, {todo_id: "b", text: long}], relations: [], + unknown_future_constraint: {stop: true}}; + const before: JsonObject = surface === "quota_packet" ? {planning_horizon: horizon} + : {action: {delivery_allowed: true, planning_horizon: horizon}}; + const path = surface === "quota_packet" ? ["planning_horizon"] : ["action", "planning_horizon"]; + const baseline = packetFull(before, "capture-a", surface); + const delta = packetWarm(before, baseline, surface); + assert.equal(delta.mode, "delta"); + for (const [key, value] of Object.entries(horizon.core_goal)) if (key !== "acceptance_contract") + assert.deepEqual(inline(delta, [...path, "core_goal", key]), value); + assert.equal(inline(delta, [...path, "core_goal", "acceptance_contract"]), undefined); + assert.equal(inline(delta, [...path, "work_items"]), undefined); + assert.deepEqual(inline(delta, [...path, "unknown_future_constraint"]), {stop: true}); + const current = structuredClone(before); + const next = (surface === "quota_packet" ? current.planning_horizon + : (current.action as JsonObject).planning_horizon) as typeof horizon; + next.core_goal.objective = "Keep the corrected original Goal and its new stop."; + next.core_goal.acceptance_contract.requirements.push("A late cumulative requirement."); + next.work_items.reverse(); + const changed = packetWarm(current, baseline, surface), capture = packetFull(current, "capture-b", surface); + assert.deepEqual(inline(changed, [...path, "core_goal", "objective"]), next.core_goal.objective); + assert.deepEqual(restoreContextDelivery({delivery: changed, baseline, current: capture}).context, current); + assert.deepEqual(restoreContextDelivery({delivery: changed, current: capture}).context, current); + } +}); + +test("Turn capsule reuses complete guidance but repeats current closure and unknown controls", () => { + const value = {action: {delivery_allowed: true}, contract_capsule: {vision_continuation_audit: { + required: true, required_before_closeout: [long], authoritative_evidence_kinds: ["source"], + not_satisfied_by: ["a receipt"], unknown_future_gate: {allowed: false}, + vision_gap_judge: {done: false, decision: "continue", external_research_instruction: long, + research_writeback_required_when_used: ["source", "finding"]}}}}; + const baseline = packetFull(value, "capture-a", "turn_envelope"); + const delta = packetWarm(value, baseline, "turn_envelope"); + assert.equal(delta.mode, "delta"); + const prefix = ["contract_capsule", "vision_continuation_audit"]; + assert.equal(inline(delta, [...prefix, "required"]), true); + assert.equal(inline(delta, [...prefix, "vision_gap_judge", "done"]), false); + assert.deepEqual(inline(delta, [...prefix, "unknown_future_gate"]), {allowed: false}); + assert.equal(inline(delta, [...prefix, "vision_gap_judge", "external_research_instruction"]), undefined); + const current = structuredClone(value); + current.contract_capsule.vision_continuation_audit.vision_gap_judge.decision = "stop"; + const changed = packetWarm(current, baseline, "turn_envelope"); + assert.equal(inline(changed, [...prefix, "vision_gap_judge", "decision"]), "stop"); + assert.deepEqual(restoreContextDelivery({delivery: changed, baseline, + current: packetFull(current, "capture-b", "turn_envelope")}).context, current); +}); + +test("registered branch evidence can be reused without retaining selection or resume authority", () => { + for (const surface of ["work_context", "quota_packet", "turn_envelope"]) { + const work = evidenceContext(), harness = source(work, 0).harness as JsonObject; + harness.selected_branches = [{todo_id: "a", selection_status: "selected", status: "open", resume_ready: true, + typed_evidence_audit: {nodes: [{node_id: "node-a", title: long}], findings: [{status: "confirmed", finding: long}]}}]; + harness.link_evidence_template = ["loopx", "todo", "update", "--todo-id", "", "--evidence", long]; + const before: JsonObject = surface === "work_context" ? work + : surface === "quota_packet" ? {interaction_contract: {agent_channel: {work_context: work}}} : {work_context: work}; + const root = surface === "work_context" ? [] : surface === "quota_packet" + ? ["interaction_contract", "agent_channel", "work_context"] : ["work_context"]; + const path = [...root, "sources", 0, "content", "harness"]; + const baseline = packetFull(before, "capture-a", surface), delta = packetWarm(before, baseline, surface); + assert.equal(delta.mode, "delta"); + assert.equal(inline(delta, [...path, "selected_branches", 0, "todo_id"]), "a"); + assert.equal(inline(delta, [...path, "selected_branches", 0, "resume_ready"]), true); + assert.equal(inline(delta, [...path, "selected_branches", 0, "typed_evidence_audit"]), undefined); + const current = structuredClone(before); + let next: unknown = current; + for (const key of [...path, "selected_branches", 0]) next = (next as Record)[key]; + const branch = next as JsonObject; + branch.selection_status = "rejected"; branch.resume_ready = false; + branch.typed_evidence_audit = {findings: [{status: "refuted", finding: "New counterexample."}]}; + const changed = packetWarm(current, baseline, surface), capture = packetFull(current, "capture-b", surface); + assert.equal(inline(changed, [...path, "selected_branches", 0, "resume_ready"]), false); + assert.deepEqual(restoreContextDelivery({delivery: changed, baseline, current: capture}).context, current); + const unchangedCapture = packetFull(before, "capture-b", surface); + // Reconstructing the same data is insufficient if unchanged live controls + // were omitted from this input rather than supplied in the current frame. + assert.equal(restoreContextDelivery({delivery: {...delta, changes: null}, baseline, + current: unchangedCapture}).mode, "full"); + } +}); + test("whole packet reuses complete projections; live controls and unknown fields remain inline", () => { const before = packet(), baseline = packetFull(before), current = structuredClone(before); const cli = ((current.interaction_contract as JsonObject).cli_channel as JsonObject); @@ -297,18 +407,20 @@ test("whole packet reuses complete projections; live controls and unknown fields assert.equal(inline(delta, ["planning_horizon"]), undefined); // A forged wire can reconstruct correctly yet omit an unchanged live gate. const broken = structuredClone(delta); - delete ((broken.changes as JsonObject).changed as JsonObject).should_run; + const changes = broken.changes as JsonObject; + delete ((changes.values as JsonObject)?.should_run !== undefined + ? changes.values as JsonObject : changes.changed as JsonObject).should_run; assert.equal(restoreContextDelivery({delivery: broken, baseline, current: packetFull(current, "capture-b")}).mode, "full"); }); test("array edits preserve append, removal, reorder, duplicate entries and exact scalar types", () => { const before = packet(), baseline = packetFull(before); - const old = (before.planning_horizon as JsonObject).items as JsonObject[]; + const old = (before.planning_horizon as JsonObject).work_items as JsonObject[]; const cases: unknown[] = [[], [old[1]], [...old, {id: "three", body: long}], [old[1], old[0]], [old[0], old[0]], [null, "", false, 0], null, "", {items: old}, [{...old[0], late_constraint: "STOP now", revision: "r2"}, old[1]]]; for (const items of cases) { - const current = {...before, planning_horizon: {items}}; + const current = {...before, planning_horizon: {work_items: items}}; const delta = packetWarm(current, baseline); const capture = packetFull(current, "capture-b"); const restored = delta.mode === "full" ? delta : restoreContextDelivery({delivery: delta, baseline, current: capture}); From de2819ee41a9742e36e4dab79a979a0d0e15a2a6 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 25 Oct 2026 04:26:28 +0800 Subject: [PATCH 2/7] fix(turn): retain Goal context for semantic replans Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/control_plane/turn_driver/driver.py | 11 ++++-- .../work_items/interaction_contract.ts | 4 +- .../test_turn_host_context_delivery.py | 2 + .../interaction_contract.test.ts | 13 +++++++ .../test_turn_route_action_classification.py | 39 ++++++++++++++++++- 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/loopx/control_plane/turn_driver/driver.py b/loopx/control_plane/turn_driver/driver.py index 20321aa457..825d84c5ba 100644 --- a/loopx/control_plane/turn_driver/driver.py +++ b/loopx/control_plane/turn_driver/driver.py @@ -160,7 +160,14 @@ def _typed_route(envelope: Mapping[str, Any]) -> LoopXTurnRoute: must_attempt = action.get("must_attempt") is True if should_run: - if not delivery_allowed or not must_attempt: + if not must_attempt: + return LoopXTurnRoute.BLOCKED + # A semantic replan forbids ordinary delivery while requiring the Host + # to produce its bound planning outcome. Keep that route distinct from + # admission to execute the selected work. + if effective_action in REPLAN_ACTIONS: + return LoopXTurnRoute.REPLAN_REQUIRED + if not delivery_allowed: return LoopXTurnRoute.BLOCKED try: registered_action = EffectiveAction(effective_action) @@ -174,8 +181,6 @@ def _typed_route(envelope: Mapping[str, Any]) -> LoopXTurnRoute: or not intent.get("command")): return LoopXTurnRoute.CONTRACT_ERROR return LoopXTurnRoute.CAPABILITY_ACTION_REQUIRED - if effective_action in REPLAN_ACTIONS: - return LoopXTurnRoute.REPLAN_REQUIRED if registered_action in REPAIR_ACTIONS: return LoopXTurnRoute.REPAIR_REQUIRED if effective_action in HOST_EXECUTION_ACTIONS: diff --git a/loopx/control_plane/work_items/interaction_contract.ts b/loopx/control_plane/work_items/interaction_contract.ts index 487a218846..1e0697c897 100644 --- a/loopx/control_plane/work_items/interaction_contract.ts +++ b/loopx/control_plane/work_items/interaction_contract.ts @@ -678,7 +678,9 @@ function interactionRequiredReads(request: JsonObject): JsonObject { const settlementOnly = requireBoolean(request.settlement_only, "settlement_only"); const checksPresent = requireBoolean(request.goal_acceptance_checks_present, "goal_acceptance_checks_present"); const goalId = readArgument(request.goal_id, "goal_id"); - if (!shouldRun || !deliveryAllowed || selectionRequired || settlementOnly || !goalId + // Semantic replan is an admitted planning attempt, even when ordinary work + // delivery is forbidden. It still needs the original Goal and acceptance. + if (!shouldRun || (!deliveryAllowed && !hasReplan) || selectionRequired || settlementOnly || !goalId || request.effective_action === EffectiveAction.GOVERNED_CAPABILITY_INTENT) { return {required_reads: reads}; } diff --git a/tests/control_plane/test_turn_host_context_delivery.py b/tests/control_plane/test_turn_host_context_delivery.py index 4c2dd1f380..e567afcd15 100644 --- a/tests/control_plane/test_turn_host_context_delivery.py +++ b/tests/control_plane/test_turn_host_context_delivery.py @@ -136,6 +136,8 @@ def test_bound_replan_reaches_actual_host_with_original_evidence(tmp_path, monke "--todo-id", SELECTED_REPLAN_TODO_ID, "--turn-instance-id", turn, "--codex-app", "--turn-envelope", "--decision-output-dir", str(capture), cwd=project) assert code == 0, envelope + assert envelope["action"]["delivery_allowed"] is False + assert envelope["action"]["must_attempt"] is True plan = build_loopx_turn_plan(envelope, host="dsh", execution_mode="isolated-headless", turn_instance_id=turn) assert plan["route"]["kind"] == "replan_required" diff --git a/tests/control_plane_ts/interaction_contract.test.ts b/tests/control_plane_ts/interaction_contract.test.ts index 0af3032b71..ee8f733192 100644 --- a/tests/control_plane_ts/interaction_contract.test.ts +++ b/tests/control_plane_ts/interaction_contract.test.ts @@ -306,6 +306,19 @@ test("shared requirement reads use final admission and add no work on non-delive ["existing", "goal_state", "goal_acceptance"]); }); +test("semantic replan retains Goal reads while ordinary delivery is forbidden", () => { + const facts: JsonObject = {...readFacts(), has_replan: true, delivery_allowed: false}; + const projected = projectInteractionRequiredReads(facts); + const reads = projected.required_reads as JsonObject[]; + assert.deepEqual(reads.map(read => read.source), ["existing", "goal_state", "goal_acceptance"]); + assert.match(String(reads[1]!.reason), /complete Goal intent, acceptance and stops/); + assert.equal(projected.delivery_allowed, undefined, "context cannot grant work authority"); + for (const patch of [{should_run: false}, {selection_required: true}, {settlement_only: true}]) { + assert.deepEqual(projectInteractionRequiredReads({...facts, ...patch}).required_reads, + facts.required_reads, JSON.stringify(patch)); + } +}); + test("shared reads follow adaptive primary and never fabricate a missing Goal source", () => { const facts: JsonObject = {...readFacts(), goal_state_file: null, goal_acceptance_checks_present: false, task_orchestration_contract: {schema_version: "task_orchestration_contract_v2", mode: "adaptive", primary_todo_id: "todo_primary"}}; diff --git a/tests/test_turn_route_action_classification.py b/tests/test_turn_route_action_classification.py index b038d959ce..ed465e3e9f 100644 --- a/tests/test_turn_route_action_classification.py +++ b/tests/test_turn_route_action_classification.py @@ -11,6 +11,8 @@ from pathlib import Path from typing import Any +import pytest + from loopx.control_plane.quota.effective_action import EffectiveAction from loopx.control_plane.quota.turn_envelope import build_turn_envelope from loopx.control_plane.turn_driver.turn_contract_generated import LoopXTurnRoute @@ -98,9 +100,42 @@ def test_repair_like_unknown_actions_do_not_gain_repair_authority() -> None: assert _route_for(action) == "ready_for_host", action -def test_replan_actions_reach_the_replan_route() -> None: +@pytest.mark.parametrize("delivery_allowed", [True, False]) +def test_replan_actions_reach_the_replan_route(delivery_allowed: bool) -> None: for action in sorted(REPLAN_ACTIONS): - assert _route_for(action) == "replan_required", action + decision = _run_decision(effective_action=action) + decision["interaction_contract"]["agent_channel"]["delivery_allowed"] = delivery_allowed + envelope = build_turn_envelope(decision) + assert envelope["action"]["delivery_allowed"] is delivery_allowed + assert _typed_route(envelope) is LoopXTurnRoute.REPLAN_REQUIRED, action + + +@pytest.mark.parametrize("control", ["should_run", "must_attempt"]) +@pytest.mark.parametrize("action", ["autonomous_replan_required", "successor_replan_required"]) +def test_replan_still_requires_an_admitted_attempt(control: str, action: str) -> None: + decision = _run_decision(effective_action=action) + channel = decision["interaction_contract"]["agent_channel"] + channel["delivery_allowed"] = False + if control == "should_run": + decision["should_run"] = False + else: + channel["must_attempt"] = False + assert _typed_route(build_turn_envelope(decision)) is LoopXTurnRoute.BLOCKED + + +@pytest.mark.parametrize("action", ["normal_run", "control_plane_repair", "future_repair"]) +def test_replan_does_not_admit_other_delivery_routes(action: str) -> None: + decision = _run_decision(effective_action=action) + decision["interaction_contract"]["agent_channel"]["delivery_allowed"] = False + assert _typed_route(build_turn_envelope(decision)) is LoopXTurnRoute.BLOCKED + + +def test_replan_still_requires_the_action_signature() -> None: + decision = _run_decision(effective_action="autonomous_replan_required") + decision["interaction_contract"]["agent_channel"]["delivery_allowed"] = False + envelope = build_turn_envelope(decision) + envelope["action_signature"]["matches"] = False + assert _typed_route(envelope) is LoopXTurnRoute.CONTRACT_ERROR def test_remaining_registered_actions_reach_the_host_route() -> None: From dfb575b52273fdb80cb8561acb88998c3dd2d17d Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:11:30 +0800 Subject: [PATCH 3/7] perf(context): compact source metadata and unify semantic replan authoring Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- docs/reference/required-work-context.md | 16 +++++++++ loopx/control_plane/quota/context_delivery.ts | 27 +++++++++++---- loopx/control_plane/quota/effect_program.py | 4 +++ loopx/control_plane/quota/settlement_plan.ts | 7 ++-- .../work_items/accountable_settlement.py | 4 +++ .../work_items/interaction_contract.py | 1 + .../work_items/interaction_contract.ts | 12 ++++--- .../work_items/replan_semantics.ts | 2 +- .../test_work_acceptance_context.py | 9 +++++ .../control_plane_ts/context_delivery.test.ts | 6 ++-- .../interaction_contract.test.ts | 34 ++++++++++++++++++- .../quota_settlement_plan.test.ts | 12 +++++++ 12 files changed, 115 insertions(+), 19 deletions(-) diff --git a/docs/reference/required-work-context.md b/docs/reference/required-work-context.md index 08be6f8f36..307ba405cc 100644 --- a/docs/reference/required-work-context.md +++ b/docs/reference/required-work-context.md @@ -46,6 +46,13 @@ selection and fulfillment policy in both ordinary packets and TurnEnvelope. `work_context.complete` means no known source failure, not that pending reads have been consumed. TurnEnvelope signs the same content and obligations; its size warning never truncates current requirements or removes a pending read. +Full progressive reads carry the complete-Goal, no-summary and fresh-guard +obligations. Only a fulfilled registered narrative adds the generated-region +and canonical-Todo ownership explanation; pending reads do not repeat that +inapplicable explanation. Todo-bound semantic replanning also uses the same +autonomous Vision authoring contract in its action and settlement projections, +so exact in-response references retain complete replacement and replan rules. +Ordinary settlement and all execution/receipt checks keep their existing rules. Preferences remain conditional on scoped records. The existing preference and Explore hooks return their actual read context through registered adapters; @@ -55,6 +62,15 @@ preserve their off-state. User Todo inventory views disappear only from exact Todo responses; scoped User decisions and gates remain in the work context and retain their existing admission/notification authority. +Todo context retains complete scoped records and canonical read provenance, +including the record schema, snapshot digest, count and provider revision. It +omits the known canonical reader's `contract_fields` dictionary: field names +describe the reader's record format, not additional task requirements. Empty +scoped User results still retain their source proof. Unknown read schemas and +malformed dictionaries remain verbatim. The explicit canonical Todo commands +keep their full read metadata; this default context compaction requires no +cross-Turn baseline and leaves all record values, gates and stops intact. + Source failure retains the unresolved read, identifies the unavailable source, and blocks dependent delivery until recovery and a fresh guard. Reading current content is not an execution grant or proof of Goal completion. diff --git a/loopx/control_plane/quota/context_delivery.ts b/loopx/control_plane/quota/context_delivery.ts index 0b279015ec..4cec6d33ea 100644 --- a/loopx/control_plane/quota/context_delivery.ts +++ b/loopx/control_plane/quota/context_delivery.ts @@ -5,6 +5,7 @@ import type {JsonObject} from "../effect_program.ts"; import {jsonObject, requireJsonObject, requireNonEmptyString} from "../runtime_decode.ts"; import {canonicalAuthorityJson, canonicalAuthoritySha256} from "../coordination/authority_store_codec.ts"; import {reusableTurnContextPaths} from "../capability_hooks.ts"; +import {COORDINATION_STATE_CONTRACT} from "../coordination/coordination_state_contract.generated.ts"; const VERSION = "context_delivery_v1"; const MAX_ANCESTRY = 32; @@ -21,14 +22,26 @@ const equal = (a: unknown, b: unknown): boolean => { const digest = (value: unknown): string => `sha256:${canonicalAuthoritySha256(value)}`; const bytes = (value: unknown): number => Buffer.byteLength(JSON.stringify(value), "utf8"); -/** A title equal to the complete body carries no second instruction. Never use - * the bounded hot title/text as the source, and retain a genuinely distinct title. */ -export function compactSelectedTodoSource(content: JsonObject): JsonObject { +/** Context keeps complete records and provenance, rather than repeating the + * canonical reader's field dictionary. Unknown read schemas remain verbatim. + * A title equal to the complete body carries no second instruction. */ +export function compactTodoContextSource(content: JsonObject): JsonObject { + let result = content; const todo = jsonObject(content.todo); - if (!todo || typeof todo.text !== "string" || todo.title !== todo.text) return content; - const copy = {...todo}; - delete copy.title; - return {...content, todo: copy}; + if (todo && typeof todo.text === "string" && todo.title === todo.text) { + const copy = {...todo}; + delete copy.title; + result = {...result, todo: copy}; + } + const authority = jsonObject(content.authority_read); + const model = jsonObject(authority?.todo_read_model); + if (model?.schema_version === COORDINATION_STATE_CONTRACT.todo_read_record.schema_version + && Array.isArray(model.contract_fields) + && model.contract_fields.every(field => typeof field === "string")) { + const {contract_fields: _dictionary, ...metadata} = model; + result = {...result, authority_read: {...authority, todo_read_model: metadata}}; + } + return result; } function parentAt(root: JsonObject, path: Path): Record { diff --git a/loopx/control_plane/quota/effect_program.py b/loopx/control_plane/quota/effect_program.py index b32e4c385f..11ac4a77c3 100644 --- a/loopx/control_plane/quota/effect_program.py +++ b/loopx/control_plane/quota/effect_program.py @@ -109,6 +109,7 @@ def build_codex_app_settlement_plan( delivery_boundary: str | None = None, quota_spend_source: str = "heartbeat", goal_ref: Mapping[str, object] | None = None, + autonomous_replan: bool = False, ) -> SettlementPlan: return build_turn_scoped_cli_settlement_plan( goal_id=goal_id, @@ -123,6 +124,7 @@ def build_codex_app_settlement_plan( delivery_boundary=delivery_boundary, quota_spend_source=quota_spend_source, goal_ref=goal_ref, + autonomous_replan=autonomous_replan, ) @@ -140,6 +142,7 @@ def build_turn_scoped_cli_settlement_plan( delivery_boundary: str | None = None, quota_spend_source: str = "heartbeat", goal_ref: Mapping[str, object] | None = None, + autonomous_replan: bool = False, ) -> SettlementPlan: if bool(todo_id) == bool(replan_obligation_id): raise ValueError( @@ -199,6 +202,7 @@ def build_turn_scoped_cli_settlement_plan( ) payload = effect_runtime_result("settlement.turn_scoped_cli_plan", { "identity": identity.as_dict(), "delivery_boundary": delivery_boundary, + "autonomous_replan": autonomous_replan, "delivery_batch_scale_choices": list(DELIVERY_BATCH_SCALE_INPUT_CHOICES), **({"goal_ref": normalized_goal_ref} if normalized_goal_ref is not None else {}), "command_templates": { diff --git a/loopx/control_plane/quota/settlement_plan.ts b/loopx/control_plane/quota/settlement_plan.ts index 03b495711e..0aa4e685ec 100644 --- a/loopx/control_plane/quota/settlement_plan.ts +++ b/loopx/control_plane/quota/settlement_plan.ts @@ -6,7 +6,7 @@ import { } from "../effect_program.ts"; import {VISION_MATERIAL_CLOSEOUT_HINT, visionAuthoringContract} from "../goals/vision_checkpoint.ts"; import {parseExactGoalRef} from "../goals/goal_instance_identity.ts"; -import {requireJsonObject, requireNonEmptyString, requireStringArray} from "../runtime_decode.ts"; +import {requireBoolean, requireJsonObject, requireNonEmptyString, requireStringArray} from "../runtime_decode.ts"; import {MATERIAL_DELIVERY_OUTCOMES} from "../work_items/delivery_outcome.ts"; import {DELIVERY_BOUNDARIES} from "../turn_driver/delivery_continuity.ts"; import {PROGRESS_RESULT_CLASSES} from "../work_items/replan_history.ts"; @@ -49,6 +49,8 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan throw new Error("settlement plan GoalRef is invalid or does not match identity"); } const inFlight = params.delivery_boundary === "in_flight_continuation"; + const autonomousReplan = params.autonomous_replan === undefined ? false + : requireBoolean(params.autonomous_replan, "autonomous_replan"); const validation: SettlementStep = { kind: "validation", owner: "agent", idempotency_key_ref: "$.identity.effect_id", expected_receipt: "validation_receipt", @@ -84,7 +86,8 @@ export function turnScopedCliSettlementPlan(params: JsonObject): SettlementPlan command_template: writeback, // In-flight continuation uses its projected flags without a material vision packet. // Autonomous replan already carries this owner in its writeback contract. - ...(identity.binding_kind === "todo" && !inFlight ? {vision_authoring: visionAuthoringContract()} : {}), + ...(identity.binding_kind === "todo" && !inFlight + ? {vision_authoring: visionAuthoringContract(autonomousReplan)} : {}), }, { kind: "quota_spend", owner: "agent", diff --git a/loopx/control_plane/work_items/accountable_settlement.py b/loopx/control_plane/work_items/accountable_settlement.py index 7fd8627b8a..88571a16a5 100644 --- a/loopx/control_plane/work_items/accountable_settlement.py +++ b/loopx/control_plane/work_items/accountable_settlement.py @@ -28,6 +28,7 @@ def build_accountable_work_item_settlement_plan( delivery_boundary: str | None = None, command_prefix: str = "loopx", goal_ref: Mapping[str, object] | None = None, + autonomous_replan: bool = False, ) -> SettlementPlan | None: if runtime_profile in APP_HEARTBEAT_SETTLEMENT_RUNTIME_PROFILES: normalized_turn_instance_id = normalize_turn_instance_id(turn_instance_id) @@ -42,6 +43,7 @@ def build_accountable_work_item_settlement_plan( turn_instance_id_ref=normalized_turn_instance_id, delivery_boundary=delivery_boundary, goal_ref=goal_ref, + autonomous_replan=autonomous_replan, ) if runtime_profile in VISIBLE_GOAL_SETTLEMENT_RUNTIME_PROFILES: normalized_turn_instance_id = normalize_turn_instance_id(turn_instance_id) @@ -59,6 +61,7 @@ def build_accountable_work_item_settlement_plan( delivery_boundary=delivery_boundary, quota_spend_source="visible-goal", goal_ref=goal_ref, + autonomous_replan=autonomous_replan, ) if runtime_profile is not SchedulerRuntimeProfile.GENERIC_CLI_AGENT_LOOP: return None @@ -76,4 +79,5 @@ def build_accountable_work_item_settlement_plan( turn_instance_id=normalized_turn_instance_id, delivery_boundary=delivery_boundary, goal_ref=goal_ref, + autonomous_replan=autonomous_replan, ) diff --git a/loopx/control_plane/work_items/interaction_contract.py b/loopx/control_plane/work_items/interaction_contract.py index b6960b9111..3eb11146c8 100644 --- a/loopx/control_plane/work_items/interaction_contract.py +++ b/loopx/control_plane/work_items/interaction_contract.py @@ -461,6 +461,7 @@ def _turn_scoped_cli_settlement_context( scoped_cli_args=scoped_cli_args, lifecycle_actor_args=f" --agent-id {shlex.quote(agent_id)}", turn_instance_id=turn_instance_id, + autonomous_replan=semantic_replan_obligation_id is not None, delivery_boundary=( str(selected_todo.get("delivery_boundary")) if selected_todo.get("delivery_boundary") diff --git a/loopx/control_plane/work_items/interaction_contract.ts b/loopx/control_plane/work_items/interaction_contract.ts index 1e0697c897..825985211a 100644 --- a/loopx/control_plane/work_items/interaction_contract.ts +++ b/loopx/control_plane/work_items/interaction_contract.ts @@ -19,7 +19,7 @@ import { AgentScopeFrontierAction } from "../agents/agent_scope_frontier.generat import { resumeConditionHasKnownPendingTarget } from "../todos/resume_condition.ts"; import { parseTodoTimestampMicros } from "../runtime_timestamp.ts"; import { monitorIsActive } from "../todos/summary_lanes.ts"; -import {compactSelectedTodoSource} from "../quota/context_delivery.ts"; +import {compactTodoContextSource} from "../quota/context_delivery.ts"; function exactBlockedSuccessorWait(request: JsonObject, monitorWait = false): JsonObject | null { const wait = jsonObject(request.vision_wait_state); @@ -700,7 +700,7 @@ function interactionRequiredReads(request: JsonObject): JsonObject { }; const stateFile = readArgument(request.goal_state_file, "goal_state_file"); if (stateFile) add(`cat -- ${shellQuote(stateFile)}`, "goal_state", - "Read complete Goal intent, acceptance and stops. A registered source may deliver all text outside explicitly generated Todo regions inline, with full-file revision and excluded-region lineage; exact canonical Todo reads own task state/claims. Otherwise consume this full progressive read. Source failure or changed requirements require a fresh guard; summaries cannot replace either read."); + "Read complete Goal intent, acceptance and stops. No summaries; failed/changed sources need a fresh guard."); if (request.goal_objective_present === true) add(`${prefix} --format json goal-acceptance inspect --goal-id ${goalArg}`, "goal_acceptance", "Use the canonical goal_objective before the older source text. Only if your adoption is missing/stale, review your own work, reconcile Todo/Vision changes, then use adopt-objective with your Agent id, exact revision/digest, own objective_work_digest and review_summary. New execution is held until your adoption. Other peers adopt independently; existing Turn settlement/release remains available. Preserve root acceptance, permissions and stops. Adopt once per objective revision, never per Turn or work/peer update. Reuse complete inline context without extra reads/writes."); if (checksPresent || request.goal_outcomes_present === true) add(`${prefix} --format json goal-acceptance inspect --goal-id ${goalArg}`, @@ -793,7 +793,7 @@ export function projectInteractionWorkContext(request: JsonObject): JsonObject { valid = (!!configuredGoalChecks(jsonObject(content!.goal_acceptance_contract)) || !!jsonObject(content!.goal_acceptance_contract)?.outcome_requirements || !!jsonObject(content!.goal_acceptance_contract)?.goal_objective); } if (valid && read.source === "selected_todo") { - content = compactSelectedTodoSource(content!); + content = compactTodoContextSource(content!); selectedTodoRecord = jsonObject(content.todo); } if (!valid) { @@ -806,7 +806,9 @@ export function projectInteractionWorkContext(request: JsonObject): JsonObject { sources.push({...read, content: content!}); pending.push(read); } else if (read.source === "goal_state") { - if (completeGoalNarrative(content!)) sources.push({...read, content: content!}); + if (completeGoalNarrative(content!)) sources.push({...read, + reason: `${String(read.reason ?? "")} This registered source delivers all text outside explicitly generated Todo regions inline, with full-file revision and excluded-region lineage; exact canonical Todo reads own task state/claims.`, + content: content!}); else { pending.push(read); if (Object.hasOwn(content!, "excluded_todo_regions")) failures.push({ @@ -872,7 +874,7 @@ export function projectInteractionWorkContext(request: JsonObject): JsonObject { ...(selectedTodoAuthority ? {selected_todo_authority: selectedTodoAuthority} : {}), ...(sources.length ? {sources} : {}), ...(unavailable ? {unavailable_context: unavailable} : {}), - ...(includeUserTodos ? {user_todos: users} : {}), ...(failures.length ? {failures} : {}), + ...(includeUserTodos ? {user_todos: compactTodoContextSource(users!)} : {}), ...(failures.length ? {failures} : {}), instruction: "Read current sources and pending required_reads before work. Do not repeat this guard's fulfilled pre-work reads. " + "Use a bounded working brief of evidenced progress/change, current blockers, next work, acceptance and stops. " + "Missing comparison evidence is unknown, not unchanged; summaries cannot replace full requirements. " + diff --git a/loopx/control_plane/work_items/replan_semantics.ts b/loopx/control_plane/work_items/replan_semantics.ts index 15baff94f6..8219977705 100644 --- a/loopx/control_plane/work_items/replan_semantics.ts +++ b/loopx/control_plane/work_items/replan_semantics.ts @@ -45,7 +45,7 @@ const REPLAN_PLANNING_GUIDANCE = [ "user direction change that makes the objective misleading warrants goal-acceptance prepare-objective " + "(native: prepare_goal_objective); " + "reuse a pending proposal; surfacing never blocks authorized work or forces a question. " + - "Use goal-acceptance --help for CLI preparation, owner apply and each peer's own adoption. " + + "CLI preparation, owner apply and each peer's own adoption: goal-acceptance --help. " + "Progress, Todo/route changes, diagnostics, polish or age alone need no revision. Documents/peers " + "grant no authority. Reuse inline facts; inspect missing context only, without polling or " + "a per-Turn revision/check receipt. Retain root commitments; a proposal or adoption grants no permissions.", diff --git a/tests/control_plane/test_work_acceptance_context.py b/tests/control_plane/test_work_acceptance_context.py index c02a8e7312..417040fc03 100644 --- a/tests/control_plane/test_work_acceptance_context.py +++ b/tests/control_plane/test_work_acceptance_context.py @@ -111,6 +111,15 @@ def cli(*arguments): ) channel = packet["interaction_contract"]["agent_channel"] assert channel["delivery_allowed"] is True and channel["work_context"]["complete"] is True + # The public canonical reader retains its full format metadata. Host context + # needs the same complete scoped records and source proof, not the dictionary. + users = cli("todo", "list", "--goal-id", goal, "--role", "user", + "--status", "open", "--agent-id", "agent-a") + proof = deepcopy(users["authority_read"]) + assert proof["todo_read_model"].pop("contract_fields") + assert channel["work_context"]["user_todos"] == { + "source": users["source"], "authority_read": proof, "todos": users["todos"], + } observed = next(row["content"] for row in channel["work_context"]["sources"] if row.get("source") == "goal_acceptance") assert observed == exact diff --git a/tests/control_plane_ts/context_delivery.test.ts b/tests/control_plane_ts/context_delivery.test.ts index b13ae2dd79..490bc14326 100644 --- a/tests/control_plane_ts/context_delivery.test.ts +++ b/tests/control_plane_ts/context_delivery.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import type {JsonObject} from "../../loopx/control_plane/effect_program.ts"; -import {compactSelectedTodoSource, prepareContextDelivery, restoreContextDelivery} from "../../loopx/control_plane/quota/context_delivery.ts"; +import {compactTodoContextSource, prepareContextDelivery, restoreContextDelivery} from "../../loopx/control_plane/quota/context_delivery.ts"; import {evaluateTurnEnvelope} from "../../loopx/control_plane/quota/turn_envelope.ts"; @@ -190,11 +190,11 @@ test("final assembly race, truncated or out-of-order delta uses same-capture ful test("cold-source title deduplication retains full body, distinct title and original capture", () => { const original = {todo: {text: long, title: long, note: "Keep this note", status: "open"}}; - const result = compactSelectedTodoSource(original); + const result = compactTodoContextSource(original); assert.deepEqual(result, {todo: {text: long, note: "Keep this note", status: "open"}}); assert.equal(original.todo.title, long); const distinct = {todo: {...original.todo, title: "A meaningful short title"}}; - assert.strictEqual(compactSelectedTodoSource(distinct), distinct); + assert.strictEqual(compactTodoContextSource(distinct), distinct); }); function evidenceContext(): JsonObject { diff --git a/tests/control_plane_ts/interaction_contract.test.ts b/tests/control_plane_ts/interaction_contract.test.ts index ee8f733192..c00662b74e 100644 --- a/tests/control_plane_ts/interaction_contract.test.ts +++ b/tests/control_plane_ts/interaction_contract.test.ts @@ -377,6 +377,33 @@ test("inline context fulfills every source without truncating acceptance or inve assert.deepEqual(request, before); }); +test("scoped User context retains every record and source proof without a record-format dictionary", () => { + const gate = {todo_id: "todo_owner", text: "Stop before deployment.", role: "user", + global_gate: true, blocks_agent: "agent-a", resume_condition: {kind: "manual"}, + unknown_future_field: {retained: true}}; + for (const todos of [[], [gate]]) { + const model = {schema_version: "loopx_todo_canonical_read_record_v0", todo_count: 9, + records_sha256: "source-snapshot", contract_fields: ["text", "global_gate", "unknown_future_field"], + unknown_metadata: {complete: false}}; + const users = {source: "sqlite_authority", todos, authority_read: { + source_authority: "sqlite_v0", provider_revision: "sqlite:snapshot:9", cursor: "9", + decision_read_from_provider: true, legacy_fallback_used: false, todo_read_model: model}}; + const original = structuredClone(users); + const projected = projectInteractionWorkContext({required_reads: [], source_results: [], user_todos: users}); + const context = projected.work_context as JsonObject; + const {contract_fields: _dictionary, ...metadata} = model; + assert.deepEqual(context.user_todos, {...users, authority_read: {...users.authority_read, todo_read_model: metadata}}); + assert.equal(context.complete, true); + assert.equal(projected.delivery_allowed, undefined); + assert.deepEqual(users, original); + for (const patch of [{schema_version: "unknown_record_schema"}, {contract_fields: ["text", 1]}]) { + const unknown = {...users, authority_read: {...users.authority_read, todo_read_model: {...model, ...patch}}}; + const result = projectInteractionWorkContext({required_reads: [], source_results: [], user_todos: unknown}); + assert.deepEqual((result.work_context as JsonObject).user_todos, unknown); + } + } +}); + test("failed, ambiguous or changed work never fulfills a pre-work read", () => { const read = {command: "read-todo", source: "selected_todo"}; for (const result of [{error_code: "unavailable"}, {content: {matched: false, todo: null}}, @@ -621,7 +648,12 @@ test("Goal narrative fulfillment needs complete source and ordered region lineag }); const fulfilled = project(content); assert.deepEqual(fulfilled.required_reads, []); - assert.deepEqual((fulfilled.work_context as JsonObject).sources, [{...read, content}]); + const [source] = (fulfilled.work_context as JsonObject).sources as JsonObject[]; + const {reason, ...captured} = source; + assert.deepEqual(captured, {...read, content}); + assert.match(String(reason), /all text outside explicitly generated Todo regions inline/); + assert.match(String(reason), /full-file revision and excluded-region lineage/); + assert.match(String(reason), /exact canonical Todo reads own task state\/claims/); for (const invalid of [ {...content, text: null}, {...content, revision: "invalid"}, {...content, state_file: ""}, {...content, excluded_todo_regions: []}, diff --git a/tests/control_plane_ts/quota_settlement_plan.test.ts b/tests/control_plane_ts/quota_settlement_plan.test.ts index 39f3b06caa..412e0f634c 100644 --- a/tests/control_plane_ts/quota_settlement_plan.test.ts +++ b/tests/control_plane_ts/quota_settlement_plan.test.ts @@ -85,6 +85,18 @@ test("in-flight progress neither executes completion nor rewrites the declaratio assert.equal(plan.steps[3].conditional, true); }); +test("Todo-bound semantic planning shares its exact autonomous authoring contract", () => { + const ordinary = turnScopedCliSettlementPlan(input); + assert.deepEqual(turnScopedCliSettlementPlan({...input, autonomous_replan: false}), ordinary); + const replan = turnScopedCliSettlementPlan({...input, autonomous_replan: true}); + assert.deepEqual(replan.steps[1].vision_authoring, visionAuthoringContract(true)); + assert.deepEqual({...replan, steps: replan.steps.map((step, i) => i === 1 + ? {...step, vision_authoring: ordinary.steps[1].vision_authoring} : step)}, ordinary); + for (const autonomous_replan of [null, "true", 1]) { + assert.throws(() => turnScopedCliSettlementPlan({...input, autonomous_replan}), /must be a boolean/); + } +}); + test("autonomous replan never manufactures a Todo completion step", () => { const plan = turnScopedCliSettlementPlan({...input, identity: {goal_id: "goal", agent_id: "agent", replan_obligation_id: "replan", turn_instance_id: "turn"}, From 0c5370b95289882bccbdcd5c1435d8e2e45371cc Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:11:31 +0800 Subject: [PATCH 4/7] test(turn): initialize canonical replan fixtures and verify complete shared sources Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../control_plane/test_effective_turn_replan.py | 17 ++++++++--------- .../test_replan_host_context_projection.py | 4 +++- .../control_plane/test_turn_replan_writeback.py | 11 ++--------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/tests/control_plane/test_effective_turn_replan.py b/tests/control_plane/test_effective_turn_replan.py index 79d62c3434..b0c72e7fea 100644 --- a/tests/control_plane/test_effective_turn_replan.py +++ b/tests/control_plane/test_effective_turn_replan.py @@ -22,8 +22,7 @@ _initialize_fixture_authority, ALTERNATIVE_TODO_ID, ) -from canonical_authority_fixture import initialize_canonical_authority, isolate_sqlite_runtime -from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection +from canonical_authority_fixture import isolate_sqlite_runtime from loopx.control_plane.work_items.replan_history_codec import ( effective_turn_cadence_context, ) @@ -47,6 +46,7 @@ def test_open_todo_settled_turn_cadence_and_evidence_linked_review( if declare_outcome: fixture["goals"][0]["status"] = "active" registry.write_text(json.dumps(fixture)) + _initialize_fixture_authority(project, runtime) if device_count is not None: machine_registry = build_builtin_machine_configuration_registry() configuration = { @@ -83,7 +83,6 @@ def test_open_todo_settled_turn_cadence_and_evidence_linked_review( ) original_outcomes = None if declare_outcome: - _initialize_fixture_authority(project, runtime) rc, basis = _run_cli(registry, runtime, "goal-acceptance", "inspect", "--goal-id", GOAL_ID) assert rc == 0, basis outcome_path = tmp_path / "ongoing-outcome.json" @@ -204,7 +203,11 @@ def periodic(): assert obligation is not None, next_guard assert "periodic_review_due" in json.dumps(obligation) if original_outcomes: - assert obligation["replan_context"]["core_goal"]["acceptance_contract"]["outcome_requirements"] == original_outcomes + core = obligation["replan_context"]["core_goal"] + assert core["acceptance_contract_ref"] == ( + "$.planning_horizon.core_goal.acceptance_contract" + ) + assert next_guard["planning_horizon"]["core_goal"]["acceptance_contract"]["outcome_requirements"] == original_outcomes assert original_outcomes["acceptance_gaps"] == [], "periodic review must include requirements covered by open work" else: assert obligation["replan_context"]["core_goal"]["acceptance_contract"] is None @@ -337,11 +340,7 @@ def test_due_stride_review_survives_successor_transition_until_decision( data = json.loads(registry.read_text()) data["goals"][0]["quota"]["allowed_slots"] = 12 registry.write_text(json.dumps(data)) - rc, listed = _run_cli(registry, runtime, "todo", "list", "--goal-id", GOAL_ID) - assert rc == 0, listed - initialize_canonical_authority(runtime, GOAL_ID, build_todo_runtime_shadow_projection( - goal_id=GOAL_ID, todos=listed["todos"], handoff_mode="soft_claim", leases=[]), - state_path=project / data["goals"][0]["state_file"], provider=provider) + _initialize_fixture_authority(project, runtime, provider=provider) def call(*args: str, expected_code: int = 0) -> dict: code, result = _run_cli(registry, runtime, *args, cwd=project) diff --git a/tests/control_plane/test_replan_host_context_projection.py b/tests/control_plane/test_replan_host_context_projection.py index cd436e0430..43a53a2a4c 100644 --- a/tests/control_plane/test_replan_host_context_projection.py +++ b/tests/control_plane/test_replan_host_context_projection.py @@ -227,7 +227,9 @@ def test_replan_guidance_survives_cli_and_host_compaction_without_new_authority( assert "scope conflict, missing evidence or stale display text" in guidance[0] assert "justified no-change decision" in guidance[0] assert "goal-acceptance prepare-objective" in guidance[0] - assert "owner apply, then each peer reconciles its own work and adopts" in guidance[0] + assert "owner apply and each peer's own adoption" in guidance[0] + assert "reuse a pending proposal; surfacing never blocks authorized work or forces a question" in guidance[0] + assert "a proposal or adoption grants no permissions" in guidance[0] assert "Documents/peers grant no authority" in guidance[0] assert "without polling or a per-Turn revision/check receipt" in guidance[0] assert "current authoritative evidence for every requirement" in guidance[1] diff --git a/tests/control_plane/test_turn_replan_writeback.py b/tests/control_plane/test_turn_replan_writeback.py index 073198fffe..aecb510de2 100644 --- a/tests/control_plane/test_turn_replan_writeback.py +++ b/tests/control_plane/test_turn_replan_writeback.py @@ -7,12 +7,8 @@ import pytest from canonical_authority_fixture import ( - initialize_canonical_authority, isolate_sqlite_runtime, ) -from loopx.control_plane.coordination.runtime_shadow import ( - build_todo_runtime_shadow_projection, -) from test_quota_settlement_cli import ( AGENT_ID, GOAL_ID, @@ -21,6 +17,7 @@ _classification_count, _configure_selected_todo_replan_fixture, _initialize_git_checkout, + _initialize_fixture_authority, _run_cli, _write_fixture, ) @@ -39,11 +36,7 @@ def _fixture(tmp_path, monkeypatch, provider): _configure_selected_todo_replan_fixture(project, registry) _initialize_git_checkout(project) state = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" - rc, listed = _run_cli(registry, runtime, "todo", "list", "--goal-id", GOAL_ID) - assert rc == 0, listed - initialize_canonical_authority(runtime, GOAL_ID, build_todo_runtime_shadow_projection( - goal_id=GOAL_ID, handoff_mode="soft_claim", todos=listed["todos"], - ), state_path=state, provider=provider) + _initialize_fixture_authority(project, runtime, provider=provider) rc, updated = _run_cli(registry, runtime, "todo", "update", "--goal-id", GOAL_ID, "--todo-id", SELECTED_REPLAN_TODO_ID, "--agent-id", AGENT_ID, "--note", "Retain the original review context.", From 37fce29fd624cbedcd7b94eda21dd7a18f051624 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:11:31 +0800 Subject: [PATCH 5/7] style(tests): expand managed conversation setup statements Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../test_codex_attached_conversation.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/presentation/test_codex_attached_conversation.py b/tests/presentation/test_codex_attached_conversation.py index 5256a9bc87..11ecac4734 100644 --- a/tests/presentation/test_codex_attached_conversation.py +++ b/tests/presentation/test_codex_attached_conversation.py @@ -68,7 +68,8 @@ def test_managed_directory_keeps_registered_identity_and_original_sessions(tmp_p assert request(path="/api/chat/sessions", query="?goal_id=sample-goal&managed_agent_id=codex")[0] == 409 store.update_session(second["session_id"], status="closed") assert [row["session_id"] for row in request(path="/api/chat/sessions", query=query)[1]["sessions"]] == [first["session_id"]] - value = json.loads(registry.read_text()); value["goals"][0]["goal_instance_id"] = "replacement-lifetime" + value = json.loads(registry.read_text()) + value["goals"][0]["goal_instance_id"] = "replacement-lifetime" registry.write_text(json.dumps(value)) assert request(path="/api/chat/sessions", query=query)[1]["sessions"] == [] assert store.load_session(legacy["session_id"]) == legacy @@ -82,10 +83,14 @@ def test_managed_directory_refuses_changed_identity_without_host_work(tmp_path, store = request.server.chat_store store.create_session(goal_id="sample-goal", agent_id="codex", adapter_kind="codex_app_server", managed_agent_id="worker", managed_workspace_path=str(tmp_path), channel_id="agent.worker", upstream_thread_id="original") - value = json.loads(registry.read_text()); goal = value["goals"][0] - if fault == "unregistered": goal["coordination"]["registered_agents"] = ["peer"] - if fault == "stopped": goal["status"] = "stopped" - if fault == "workspace": goal["repo"] = str(tmp_path / "different") + value = json.loads(registry.read_text()) + goal = value["goals"][0] + if fault == "unregistered": + goal["coordination"]["registered_agents"] = ["peer"] + if fault == "stopped": + goal["status"] = "stopped" + if fault == "workspace": + goal["repo"] = str(tmp_path / "different") registry.write_text(json.dumps(value)) status, result = request(path="/api/chat/sessions", query=f"?goal_id={'foreign' if fault == 'foreign-goal' else 'sample-goal'}&managed_agent_id=worker") if fault == "workspace": From 4fe4fe0b822dc8b6325b6f215943fc0aba6fbf99 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:31:19 +0800 Subject: [PATCH 6/7] fix(context): retain Goal read guidance within Windows output budget Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- loopx/control_plane/work_items/interaction_contract.ts | 2 +- tests/control_plane_ts/interaction_contract.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/loopx/control_plane/work_items/interaction_contract.ts b/loopx/control_plane/work_items/interaction_contract.ts index 825985211a..e3da57ad50 100644 --- a/loopx/control_plane/work_items/interaction_contract.ts +++ b/loopx/control_plane/work_items/interaction_contract.ts @@ -700,7 +700,7 @@ function interactionRequiredReads(request: JsonObject): JsonObject { }; const stateFile = readArgument(request.goal_state_file, "goal_state_file"); if (stateFile) add(`cat -- ${shellQuote(stateFile)}`, "goal_state", - "Read complete Goal intent, acceptance and stops. No summaries; failed/changed sources need a fresh guard."); + "Read full Goal intent, acceptance, stops. No summaries; failed/changed sources need a fresh guard."); if (request.goal_objective_present === true) add(`${prefix} --format json goal-acceptance inspect --goal-id ${goalArg}`, "goal_acceptance", "Use the canonical goal_objective before the older source text. Only if your adoption is missing/stale, review your own work, reconcile Todo/Vision changes, then use adopt-objective with your Agent id, exact revision/digest, own objective_work_digest and review_summary. New execution is held until your adoption. Other peers adopt independently; existing Turn settlement/release remains available. Preserve root acceptance, permissions and stops. Adopt once per objective revision, never per Turn or work/peer update. Reuse complete inline context without extra reads/writes."); if (checksPresent || request.goal_outcomes_present === true) add(`${prefix} --format json goal-acceptance inspect --goal-id ${goalArg}`, diff --git a/tests/control_plane_ts/interaction_contract.test.ts b/tests/control_plane_ts/interaction_contract.test.ts index c00662b74e..48ebd90513 100644 --- a/tests/control_plane_ts/interaction_contract.test.ts +++ b/tests/control_plane_ts/interaction_contract.test.ts @@ -311,7 +311,7 @@ test("semantic replan retains Goal reads while ordinary delivery is forbidden", const projected = projectInteractionRequiredReads(facts); const reads = projected.required_reads as JsonObject[]; assert.deepEqual(reads.map(read => read.source), ["existing", "goal_state", "goal_acceptance"]); - assert.match(String(reads[1]!.reason), /complete Goal intent, acceptance and stops/); + assert.match(String(reads[1]!.reason), /(?:full|complete) Goal intent, acceptance(?: and|,) stops/); assert.equal(projected.delivery_allowed, undefined, "context cannot grant work authority"); for (const patch of [{should_run: false}, {selection_required: true}, {settlement_only: true}]) { assert.deepEqual(projectInteractionRequiredReads({...facts, ...patch}).required_reads, From 442013a0c516bad2d93ec6e75483591162def4ef Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Sun, 25 Oct 2026 17:38:09 +0800 Subject: [PATCH 7/7] fix(context): prepare saved projection caches at the shared entrypoint Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- benchmark/runtime/context_delivery.py | 3 +- docs/reference/required-work-context.md | 20 +++++++ loopx/cli_commands/quota_capture.py | 14 +++-- .../test_saved_context_project.py | 54 ++++++++++++++++++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/benchmark/runtime/context_delivery.py b/benchmark/runtime/context_delivery.py index bf0fe1ce63..7c60e8fbf7 100644 --- a/benchmark/runtime/context_delivery.py +++ b/benchmark/runtime/context_delivery.py @@ -25,8 +25,7 @@ def preflight_context_instruction(env: dict[str, str], path: Path, digest: str, source = ["--capture-file", str(path), "--capture-sha256", digest] project = [*prefix, "context-project", *source, "--goal-id", env["LOOPX_GOAL_ID"], "--agent-id", env["LOOPX_AGENT_ID"], - "--turn-instance-id", turn_id, "--context-delivery", "on", "--runtime-profile", "generic_cli", "--codex-context-dir", env["LOOPX_CODEX_CONTEXT_DIR"], - "--decision-output-root", str(path.parent / "decisions")] + "--turn-instance-id", turn_id, "--context-delivery", "on", "--runtime-profile", "generic_cli", "--codex-context-dir", env["LOOPX_CODEX_CONTEXT_DIR"]] full = [*prefix, "context-read", *source] return ("Present that same immutable packet with:\n```sh\n" + shlex.join(project) + "\n```\nFollow the returned command and all next_command pages; reconstruct any delta against " diff --git a/docs/reference/required-work-context.md b/docs/reference/required-work-context.md index f6bcc90d83..d499705d8f 100644 --- a/docs/reference/required-work-context.md +++ b/docs/reference/required-work-context.md @@ -230,3 +230,23 @@ TurnEnvelope。确认基线保留后,两入口仍逐轮发送 `planning_horizo 这些只改变既有选项内的展示,不新增配置或准入。保留不明、epoch 改变、patch 损坏 或读者不兼容时,自动恢复同一原始 full capture,不重跑 guard、不问询、不扣额, 也不授予执行权限。传输减少本身不证明实际模型费用或结果改善。 + +### Saved context cache / 已保存上下文缓存 + +For `quota context-project`, enabling context delivery no longer requires a +second cache-directory setup step. Omitting `--decision-output-root` reuses the +native session's private context directory. An explicit missing leaf is created +with mode `0700` under an existing parent; existing directories must remain +private, physically addressed and owned by the current user. Existing symlinks, +non-directories and unsafe permissions are rejected without changing them. +Normal platform parent aliases are resolved before validating the leaf. +Read-only recovery never creates a cache. With context delivery off, projection +remains inert and reads the original capture. Failures retain that capture's +exact digest and Turn receipt; they never rerun admission or spend quota. + +`quota context-project` 开启上下文投递后,不再要求另外手工准备缓存目录。 +省略 `--decision-output-root` 时复用原生会话的私有上下文目录;显式指定的新目录 +在已存在的父目录下以 `0700` 创建。已有目录仍须属于当前用户、权限私有且不是 +符号链接;不修改不安全目录的权限。平台正常的父路径别名先解析再检查末级目录。 +只读恢复不创建缓存;关闭上下文投递时保持无副作用,读取原始 capture。失败仍保留 +原始摘要和 Turn 回执,不重新准入或扣减额度。 diff --git a/loopx/cli_commands/quota_capture.py b/loopx/cli_commands/quota_capture.py index 2cc83c7e63..ca5b8fb38e 100644 --- a/loopx/cli_commands/quota_capture.py +++ b/loopx/cli_commands/quota_capture.py @@ -152,14 +152,20 @@ def project_saved_context_capture(args: argparse.Namespace) -> dict[str, Any]: if not activation["enabled"]: raise ValueError("context delivery is off") - root = Path(args.decision_output_root).expanduser().absolute() - metadata = root.lstat() - if root.resolve(strict=True) != root or not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.getuid() or metadata.st_mode & 0o077: - raise ValueError("saved projection needs an existing private physical capture root") directory = codex_context_directory(directory=args.codex_context_dir, root=args.codex_context_root, create=True) if not directory: raise ValueError("native context is unavailable") + # The session already owns a private recovery location. Callers need + # only supply a separate cache root when they want another location. + # Create a missing leaf privately; never chmod or follow an existing + # leaf. Normal platform parent aliases (such as /tmp) remain usable. + root = Path(args.decision_output_root or directory).expanduser().absolute() + root = root.parent.resolve(strict=True) / root.name + root.mkdir(mode=0o700, exist_ok=True) + metadata = root.lstat() + if not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.getuid() or metadata.st_mode & 0o077: + raise ValueError("saved projection needs a private physical capture root owned by this user") cache = Path(tempfile.mkdtemp(prefix="context-", dir=root)) projected = project_codex_context(payload, directory=directory, capture_directory=cache, goal_id=args.goal_id, agent_id=args.agent_id, surface="quota_packet", diff --git a/tests/control_plane/test_saved_context_project.py b/tests/control_plane/test_saved_context_project.py index 3b4a66a6c7..6dcb87f6ad 100644 --- a/tests/control_plane/test_saved_context_project.py +++ b/tests/control_plane/test_saved_context_project.py @@ -44,7 +44,7 @@ def test_saved_projection_checks_original_before_any_publication(tmp_path, failu assert code == 0 and page["content"].encode() == path.read_bytes()[:page["next_offset"]] -@pytest.mark.parametrize("root_kind", ["unsafe", "symlink", "missing"]) +@pytest.mark.parametrize("root_kind", ["unsafe", "symlink", "missing_parent"]) def test_enabled_cache_preparation_failure_returns_error_with_original_reference(tmp_path, monkeypatch, root_kind): from loopx.cli import build_parser from loopx.cli_commands.quota_capture import project_saved_context_capture @@ -66,6 +66,8 @@ def test_enabled_cache_preparation_failure_returns_error_with_original_reference root.chmod(0o755) elif root_kind == "symlink": root.symlink_to(context, target_is_directory=True) + if root_kind == "missing_parent": + root = root / "uncreated-parent" / "cache" digest = hashlib.sha256(raw).hexdigest() args = build_parser().parse_args(["--format", "json", "quota", "context-project", "--capture-file", str(path), "--capture-sha256", digest, "--goal-id", "saved-goal", @@ -78,6 +80,55 @@ def test_enabled_cache_preparation_failure_returns_error_with_original_reference assert path.read_bytes() == raw and stale.read_bytes() == stale_bytes + +@pytest.mark.parametrize("enabled", [False, True]) +@pytest.mark.parametrize("cache_location", ["default", "new", "existing"]) +def test_saved_projection_manages_private_cache_without_authority_io(tmp_path, monkeypatch, enabled, cache_location): + from loopx.control_plane.turn_driver.codex_context import read_codex_context_reference + + context, root = tmp_path / "native", tmp_path / "cache" + context.mkdir(mode=0o700) + if cache_location == "existing": + root.mkdir(mode=0o700) + path = tmp_path / "original.json" + original = {"ok": True, "goal_id": "saved-goal", "agent_identity": {"agent_id": "saved-agent"}, + "heartbeat_receipt": {"turn_instance_id": "saved-turn"}, + "requirement": "Retain every requirement and the original decision identity."} + raw = json.dumps(original).encode() + path.write_bytes(raw) + digest = hashlib.sha256(raw).hexdigest() + monkeypatch.setenv("CODEX_THREAD_ID", "native-session") + monkeypatch.chdir(tmp_path) + if enabled: + from loopx.control_plane.turn_driver.codex_context import codex_context_hook + codex_context_hook(str(context), {"hook_event_name": "SessionStart", "source": "startup", + "session_id": "native-session", "cwd": str(tmp_path)}) + registry, runtime = tmp_path / "absent-registry.json", tmp_path / "absent-runtime" + args = ["quota", "context-project", "--capture-file", str(path), "--capture-sha256", digest, + "--goal-id", "saved-goal", "--agent-id", "saved-agent", "--turn-instance-id", "saved-turn", + "--codex-app", "--context-delivery", "on" if enabled else "off", "--codex-context-dir", str(context)] + if cache_location != "default": + args += ["--decision-output-root", str(root)] + code, result = _run_cli(registry, runtime, *args, cwd=tmp_path) + assert code == 0 and result["ok"] and result["mode"] == "full", result + assert path.read_bytes() == raw and not registry.exists() and not runtime.exists() + if enabled: + actual_root = context if cache_location == "default" else root + assert actual_root.stat().st_mode & 0o077 == 0 + assert list(actual_root.glob("context-*")) + recovered_path, recovered_digest = read_codex_context_reference(str(context), + goal_id="saved-goal", agent_id="saved-agent") + recovered = Path(recovered_path).read_bytes() + assert hashlib.sha256(recovered).hexdigest() == recovered_digest + assert json.loads(recovered) == original + else: + assert not list(context.iterdir()) + assert root.exists() == (cache_location == "existing") + command = shlex.split(result["command"]) + code, page = _run_cli(registry, runtime, *command[3:], cwd=tmp_path) + assert code == 0 and json.loads(page["content"]) == original + + def test_saved_projection_wire_failure_keeps_committed_current_recovery(tmp_path, monkeypatch): from loopx.cli import build_parser from loopx.cli_commands.quota_capture import project_saved_context_capture @@ -183,6 +234,7 @@ def test_preflight_instruction_pins_both_projection_and_full_fallback(tmp_path): assert argv[argv.index("--capture-sha256") + 1] == "a" * 64 assert "should-run" not in argv and "spend-slot" not in argv assert commands[0][commands[0].index("--turn-instance-id") + 1] == "original-turn" + assert "--decision-output-root" not in commands[0] @pytest.mark.skipif(not os.environ.get("LOOPX_NATIVE_CHECKPOINT_BIN"), reason="Explicit native Codex; loopback inference only")