Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions loopx/control_plane/quota/progressive_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ function nativeContextContinuity(codec: JsonObject): JsonObject {
/** Plan and encode one native capture without sending its intermediate view
* back through Python. Capture publication and native locking remain Host IO. */
export function prepareProgressiveContextDelivery(request: JsonObject, legacyFields: readonly string[],
decisionFields: readonly string[], plan?: JsonObject): JsonObject {
decisionOrder: InitialContextDecisionOrder, plan?: JsonObject): JsonObject {
if (request.include_initial_read_plan !== undefined)
requireBoolean(request.include_initial_read_plan, "include_initial_read_plan");
const sourceValues: ProjectionSourceValues = new WeakMap();
Expand All @@ -462,7 +462,7 @@ export function prepareProgressiveContextDelivery(request: JsonObject, legacyFie
if (request.source_edits === true && delivery.mode === "full") delete delivery.context;
const observe = request.observe_first_read === true && !!jsonObject(required.context.selected_todo)?.todo_id;
const groups = observe || request.include_initial_read_plan === true
? initialContextReadGroups(required.context, decisionFields) : undefined;
? initialContextReadGroups(required.context, decisionOrder) : undefined;
const pointers = groups ? [...groups.opening, ...groups.remaining] : [];
const observed = observe && pointers.length ? observedFirstRead(codec, pointers) : undefined;
const {operation: _operation, source_edits: _edits, source_number_tokens: _numbers, ...source} = request;
Expand Down Expand Up @@ -493,12 +493,17 @@ function nativeContextReadPlan(context: JsonObject, pointers: string[],
? {opening: select(groups.opening), remaining: select(groups.remaining)} : {})};
}

export function initialContextReadPointers(context: JsonObject, decisionFields: readonly string[]): string[] {
const {opening, remaining} = initialContextReadGroups(context, decisionFields);
interface InitialContextDecisionOrder {
fields: readonly string[];
afterReadyInteraction?: string;
}

export function initialContextReadPointers(context: JsonObject, order: InitialContextDecisionOrder): string[] {
const {opening, remaining} = initialContextReadGroups(context, order);
return [...opening, ...remaining];
}

function initialContextReadGroups(context: JsonObject, decisionFields: readonly string[]): {
function initialContextReadGroups(context: JsonObject, {fields, afterReadyInteraction}: InitialContextDecisionOrder): {
opening: string[]; remaining: string[];
} {
const object = (value: unknown): JsonObject | undefined =>
Expand All @@ -524,7 +529,16 @@ function initialContextReadGroups(context: JsonObject, decisionFields: readonly
&& unread.pointers.every(pointer => typeof pointer === "string")
&& typeof supportingSource?.path === "string" && typeof supportingSource.sha256 === "string"
&& typeof details?.instruction === "string" && details.instruction.length > 0;
for (const field of decisionFields) include(field);
const channel = object(interaction.agent_channel), cli = object(interaction.cli_channel);
const prepared = object(cli?.execution_preparation);
const ready = context.should_run === true && context.effective_action === EffectiveAction.NORMAL_RUN
&& context.normal_delivery_allowed === true && channel?.delivery_allowed === true
&& prepared?.state === "ready" && prepared.delivery_allowed === true
&& cli?.selection_required !== true && cli?.turn_binding_required !== true;
// In a ready Turn, deliver the bound decision and required reads before
// a potentially long authority block. Keep the entire block in the opening.
const deferredField = ready ? afterReadyInteraction : undefined;
for (const field of fields) if (field !== deferredField) include(field);
// A staged replan has already replaced inline acceptance with a pinned
// review reader. Deliver its first action and ordering without writeback.
const replan = object(context.replan_action_packet);
Expand Down Expand Up @@ -560,14 +574,14 @@ function initialContextReadGroups(context: JsonObject, decisionFields: readonly
include("interaction_contract", "user_channel");
for (const field of ["primary_action", "delivery_allowed", "must_attempt", "quiet_noop_allowed", "next_task_action"])
include("interaction_contract", "agent_channel", field);
const turnBindingRequired = object(interaction.cli_channel)?.turn_binding_required === true;
const turnBindingRequired = cli?.turn_binding_required === true;
if (turnBindingRequired)
for (const field of ["turn_binding_required", "next_cli_actions", "spend_allowed_now", "spend_after_validation", "spend_policy"])
include("interaction_contract", "cli_channel", field);
include("interaction_contract", "agent_channel", "required_reads");
const work = ["interaction_contract", "agent_channel", "work_context"];
include(...work, "instruction");
const channel = object(interaction.agent_channel);
if (deferredField && fields.includes(deferredField)) include(deferredField);
const sources = object(channel?.work_context)?.sources;
const sourceRows = Array.isArray(sources) ? sources.map((source, index) => ({source, index})) : [];
const selectedTodo = object(context.selected_todo);
Expand Down Expand Up @@ -683,13 +697,11 @@ function initialContextReadGroups(context: JsonObject, decisionFields: readonly
include(...source, "content", "harness", "guidance");
include(...source, "content", "harness", "orchestration_gate");
});
const cli = object(interaction.cli_channel);
const planPath = ["interaction_contract", "cli_channel", "settlement_plan"];
const plan = object(cli?.settlement_plan);
const steps = (plan?.schema_version === "quota_settlement_plan_v1" || plan?.schema_version === "quota_settlement_plan_v2")
&& Array.isArray(plan.ordered_steps)
? plan.ordered_steps : undefined;
const prepared = object(cli?.execution_preparation);
const identity = object(plan?.identity);
const settlementSteps = steps?.map(object).filter(step => step?.kind === "durable_writeback" || step?.kind === "quota_spend");
// A ready Turn already shows its ordered writeback and spend with their
Expand Down
5 changes: 3 additions & 2 deletions loopx/control_plane/quota/turn_envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2214,6 +2214,7 @@ const INITIAL_CONTEXT_DECISION_FIELDS = ["ok", "schema_version", "goal_id", "age
"normal_delivery_allowed", "recovery_delivery_allowed", "self_repair_allowed", "capability_repair_allowed", "workspace_repair_allowed",
"safe_bypass_allowed", "safe_bypass_kind", "blocked_action_scope", "reason",
"execution_obligation", "goal_boundary", "heartbeat_receipt", "work_lane_contract", "selected_todo"];
const INITIAL_CONTEXT_DECISION_ORDER = {fields: INITIAL_CONTEXT_DECISION_FIELDS, afterReadyInteraction: "goal_boundary"};

/** The read-only plan needs the current decision and executable authoring
* guidance. Evidence and diagnostics remain in its exact source capture. */
Expand Down Expand Up @@ -2259,7 +2260,7 @@ function capturedDecisionSelection(request: JsonObject): JsonObject {
const initial = request.initial_read === true || request.list_fields === true
&& Array.isArray(request.select) && request.select.length === 1 && request.select[0] === "";
return capturedContextSelection(request, initial ? initialContextReadPointers(
requireJsonObject(request.context, "captured context"), INITIAL_CONTEXT_DECISION_FIELDS) : []);
requireJsonObject(request.context, "captured context"), INITIAL_CONTEXT_DECISION_ORDER) : []);
}

function replanCliView(payload: JsonObject, identity: ReplanValueIdentity = canonicalHash,
Expand Down Expand Up @@ -2507,7 +2508,7 @@ export function evaluateTurnEnvelope(value: unknown): JsonObject {
return prepareProgressiveContext(request, checkedContextPresentationPlan(request));
if (operation === "context_delivery") return request.native_projection === undefined
? prepareContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS)
: prepareProgressiveContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS, INITIAL_CONTEXT_DECISION_FIELDS,
: prepareProgressiveContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS, INITIAL_CONTEXT_DECISION_ORDER,
checkedContextPresentationPlan(request));
if (operation === "native_context_lifecycle") return updateNativeContextLifecycle(request);
if (operation === "context_recovery") return restoreContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS);
Expand Down
29 changes: 25 additions & 4 deletions tests/control_plane/test_context_first_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,10 @@ def test_cli_current_goal_and_action_precede_complete_scoped_preferences(tmp_pat


@pytest.mark.parametrize("provider", ["file", "sqlite"])
def test_advertised_native_reader_keeps_the_complete_current_contract_across_pages(tmp_path, monkeypatch, provider):
@pytest.mark.parametrize("wide_boundary", [False, True])
def test_advertised_native_reader_keeps_the_complete_current_contract_across_pages(
tmp_path, monkeypatch, provider, wide_boundary,
):
from test_quota_settlement_cli import (
AGENT_ID, GOAL_ID, TODO_ID, _heartbeat_receipt_count, _initialize_fixture_authority,
_run_cli, _spend_run_count, _write_fixture,
Expand All @@ -467,6 +470,11 @@ def test_advertised_native_reader_keeps_the_complete_current_contract_across_pag
isolate_sqlite_runtime(tmp_path, monkeypatch)
monkeypatch.delenv("CODEX_THREAD_ID", raising=False)
project, runtime, registry = _write_fixture(tmp_path)
if wide_boundary:
scopes = [f"src/feature-{index:03}/nested/acceptance/canonical-output/**" for index in range(270)]
registry_body = json.loads(registry.read_text())
registry_body["goals"][0]["coordination"]["write_scope"] = scopes
registry.write_text(json.dumps(registry_body))
_initialize_fixture_authority(project, runtime, provider=provider, handoff_mode="hard_lease")
code, claim = _run_cli(registry, runtime, "todo", "claim", "--goal-id", GOAL_ID,
"--todo-id", TODO_ID, "--agent-id", AGENT_ID, "--claimed-by", AGENT_ID, cwd=project)
Expand All @@ -481,21 +489,34 @@ def test_advertised_native_reader_keeps_the_complete_current_contract_across_pag
_, reference = read_context_read_reference(view["recovery"]["reference_file"])
raw = Path(reference["path"]).read_bytes()
source = json.loads(raw)
page_limit = 12000 if wide_boundary else 4500
words = shlex.split(view["first_read_command"])
for option in ("--page-bytes", "--max-page-output-bytes"):
words[words.index(option) + 1] = "4500"
words[words.index(option) + 1] = str(page_limit)
content = []
for _ in range(20):
code, page = _run_cli(registry, runtime, *words[words.index("quota"):], cwd=project)
assert code == 0 and page["ok"], page
assert len((json.dumps(page, ensure_ascii=False, separators=(",", ":")) + "\n").encode()) <= 4500
assert len((json.dumps(page, ensure_ascii=False, separators=(",", ":")) + "\n").encode()) <= page_limit
assert capture_page_range(page, raw=raw, digest=reference["sha256"]) is None
content.append(page["content"])
if page["eof"]:
break
words = shlex.split(page["next_command"])
assert page["eof"] and len(content) > 1
values = selected_context_values("".join(content))
selection = "".join(content)
values = selected_context_values(selection)
if wide_boundary:
assert source["execution_preparation"]["state"] == "ready"
boundary = source["goal_boundary"]
assert set(scopes).issubset(boundary["write_scope"])
assert len(json.dumps(boundary).encode()) > page_limit
assert values["/goal_boundary"] == boundary
rows = json.loads(selection)["selections"]
for field in ("/heartbeat_receipt", "/selected_todo", "/interaction_contract/agent_channel/required_reads"):
row = next(row for row in rows
if field == row["pointer"] or field.startswith(row["pointer"] + "/"))
assert json.dumps(row, ensure_ascii=False, separators=(",", ":")) in content[0]
cli_path = "/interaction_contract/cli_channel"
expected_cli = selected_context_values(json.dumps({"selections": [
{"pointer": cli_path, "value": source["interaction_contract"]["cli_channel"]}]}))
Expand Down
18 changes: 10 additions & 8 deletions tests/control_plane_ts/progressive_context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {visionAuthoringContract} from "../../loopx/control_plane/goals/vision_ch
import {initialContextReadPointers, progressiveContextPlan} from "../../loopx/control_plane/quota/progressive_context.ts";
import {FULL_OUTCOME_READ_GUIDANCE, PROGRESSIVE_OUTCOME_READ_GUIDANCE} from "../../loopx/control_plane/work_items/interaction_contract.ts";

const noDecisionFields = {fields: []};

const outcome = {id: "o", objective: "Keep all requirements.", revision: 1, owner_agent_id: "a", todo_ids: ["t"],
sources: [{ref: "original", summary: "Supporting history."}], requirements: [{id: "r", description: "Final stop.",
status: "unverified", planning_satisfied: false, agent_review: {id: "r", conclusion: "unmet", note: "Why it failed.",
Expand All @@ -22,15 +24,15 @@ test("initial navigation keeps unknown supporting-index obligations whole", () =
const context = {interaction_contract: {schema_version: "loopx_interaction_contract_v0"},
context_details: {instruction: "Read applicable supporting evidence before dependent work.", unread_context_index: index}};
const before = structuredClone(context);
assert.ok(initialContextReadPointers(context, []).includes(base));
assert.ok(initialContextReadPointers(context, noDecisionFields).includes(base));
assert.deepEqual(context, before);
}
assert.ok(initialContextReadPointers({interaction_contract: {schema_version: "loopx_interaction_contract_v0"},
context_details: {unread_context_index: known}}, []).includes(base));
assert.deepEqual(initialContextReadPointers({interaction_contract: {schema_version: "loopx_interaction_contract_v0"}}, []), []);
context_details: {unread_context_index: known}}, noDecisionFields).includes(base));
assert.deepEqual(initialContextReadPointers({interaction_contract: {schema_version: "loopx_interaction_contract_v0"}}, noDecisionFields), []);
for (const index of [known, {...known, source_blocks: ["/supporting/source/content"]}]) {
const pointers = initialContextReadPointers({interaction_contract: {schema_version: "loopx_interaction_contract_v0"},
context_details: {instruction: "Read applicable evidence before dependent work.", unread_context_index: index}}, []);
context_details: {instruction: "Read applicable evidence before dependent work.", unread_context_index: index}}, noDecisionFields);
assert.ok(pointers.includes(`${base}/source`));
assert.ok(pointers.includes(`${base}/read_before_dependent_work`));
assert.ok(pointers.includes("/context_details/instruction"));
Expand All @@ -53,7 +55,7 @@ test("initial outcome ids carry only verified supporting-material addresses", ()
source: {path: "complete.json", sha256: "fixture-digest"}}}};
const canonicalAlias = base + "/0/supporting_material_pointer";
const before = structuredClone(context);
const first = initialContextReadPointers(context, []);
const first = initialContextReadPointers(context, noDecisionFields);
assert.ok(first.includes(base + "/0/id") && !first.includes(canonicalAlias));
assert.ok(first.includes(base + "/1/supporting_material_pointer"), "preserve an explicit different address");
assert.ok(first.includes(base + "/2/supporting_material_pointer"), "a missing id cannot carry the address");
Expand All @@ -63,7 +65,7 @@ test("initial outcome ids carry only verified supporting-material addresses", ()
{...context.context_details, path: "different.json"}, {...context.context_details, sha256: "different-digest"},
{...context.context_details, instruction: undefined}, {...context.context_details,
unread_context_index: {...context.context_details.unread_context_index, future_obligation: true}}]) {
assert.ok(initialContextReadPointers({...context, context_details: changed}, []).includes(canonicalAlias));
assert.ok(initialContextReadPointers({...context, context_details: changed}, noDecisionFields).includes(canonicalAlias));
}
});

Expand Down Expand Up @@ -171,11 +173,11 @@ test("first read keeps decision rules and preserves nonstandard Vision obligatio
const context = {interaction_contract: {schema_version: "loopx_interaction_contract_v0", cli_channel: {
settlement_plan: {schema_version: "quota_settlement_plan_v1", ordered_steps: [required]},
}}};
const pointers = initialContextReadPointers(context, []);
const pointers = initialContextReadPointers(context, noDecisionFields);
assert.ok(pointers.includes(base + "/rule"));
assert.equal(pointers.includes(base + "/continuation_review"), Object.hasOwn(schema, "continuation_review"));
assert.ok(!pointers.includes(base));
required.vision_authoring = {...schema, new_stop: "Keep this changed policy visible."};
assert.ok(initialContextReadPointers(context, []).includes(base));
assert.ok(initialContextReadPointers(context, noDecisionFields).includes(base));
}
});
Loading