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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,16 @@ unchanged. This does not qualify C1's unified producer/consumer cutover or the
matched effectiveness/full-cost result in §9. Rollback uses the existing
context-delivery off path; staged material composition remains separately owned.

Native delivery now composes the existing progressive plan and full/delta codec
in one TypeScript invocation. The intermediate Python plan application and its
dispatch are retired. Python retains the session lock, Host observations and
private immutable-file publication: it saves the current complete recovery
before the optional transaction, then publishes any required view before
returning its encoded delivery. Original numeric spellings and source-reference
identity use the existing projection-edit transport. This changes neither
required-read obligations nor admission, settlement or the off path; it does
not close C1/C2 adoption or establish model-outcome/full-cost benefit.

C1's inventory is part of its usable replacement, not a standalone architecture
framework. C3/C4/C5 can proceed independently once C1's contract is stable;
C7 can prepare measurements before C6 but cannot claim two-session outcomes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,13 @@ backoff 继续归当前 scheduler owner。本文不引入模型空轮询或新
第 9 节可比效果/完整成本验收完成。沿既有 context-delivery off 路径回滚;材料分阶段
组合仍由原 owner 独立收敛。

原生交付现在在一次 TypeScript 调用内组合既有渐进交付计划和 full/delta codec,
退役中间的 Python 计划应用流程及其 dispatch。Python 保留会话锁、Host 观察和
私有不可变文件发布:先保存当前完整恢复源,再执行可选事务;如需必读视图,
在返回编码结果前发布该视图。原始数值写法和来源引用身份复用既有投影编辑传输。
本改动不改变必读义务、准入、结算或关闭路径,也不代表 C1/C2 采用资格或
模型结果与完整成本收益已经验收。

C1 盘点是可用替换的一部分,不单独建设架构框架。C1 契约稳定后,C3/C4/C5 可以独立
推进;C7 可以提前准备测量,但不能提前宣称双会话成果。实现 PR 列明替换的调用方、剩余
缺口、实际测试与回退。完整语言迁移或无关 manager 重写不是前置条件;新抽象能编译
Expand Down
15 changes: 11 additions & 4 deletions loopx/control_plane/quota/context_delivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,13 +581,20 @@ function publish(request: JsonObject, result: JsonObject, values: ProjectionSour
/** Hosts may confirm final-input retention, or explicitly select the native
* hook delivery assumption. These bases are distinct; neither grants authority. */
export function prepareContextDelivery(request: JsonObject, legacyQuotaFields: readonly string[] = []): JsonObject {
const values: ProjectionSourceValues = new WeakMap();
return publish(request, encodeContextDelivery(request, legacyQuotaFields, values), values);
}

/** Shared encoding within one presentation transaction; callers publish once
* against their original source so numeric facts never round-trip through JS. */
export function encodeContextDelivery(request: JsonObject, legacyQuotaFields: readonly string[],
values: ProjectionSourceValues): JsonObject {
const context = requireJsonObject(request.context, "current projection");
const identity = contextSourceIdentity(request, request.source_number_tokens);
const values: ProjectionSourceValues = new WeakMap();
const surface = surfaceOf(request.surface ?? "work_context"), target = targetBinding(request, context, surface);
const full = (reason: string): JsonObject => ({schema_version: VERSION, mode: "full", target,
ancestry: 0, reason, context: request.source_edits === true ? context : clone(context)});
const fullResult = (reason: string): JsonObject => publish(request, full(reason), values);
const fullResult = full;
const baseline = jsonObject(request.baseline), retained = jsonObject(request.retained);
const continuity = jsonObject(request.continuity);
const binding = jsonObject(baseline?.target), prior = jsonObject(baseline?.context);
Expand Down Expand Up @@ -636,8 +643,8 @@ export function prepareContextDelivery(request: JsonObject, legacyQuotaFields: r
// Native consumers page the wire before they can use its baseline. Deliver
// the existing basis and interpretation before the potentially large tree;
// do not duplicate them in a second host instruction or change strict IO.
return publish(request, assumed || fileBacked ? {schema_version: VERSION, mode: "delta",
continuity_basis: delta.continuity_basis, instruction: delta.instruction, ...delta} : delta, values);
return assumed || fileBacked ? {schema_version: VERSION, mode: "delta",
continuity_basis: delta.continuity_basis, instruction: delta.instruction, ...delta} : delta;
}

/** Validate the exact change tree against the immutable CURRENT capture before
Expand Down
105 changes: 104 additions & 1 deletion loopx/control_plane/quota/progressive_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import type {JsonObject} from "../effect_program.ts";
import {jsonObject, requireJsonObject} from "../runtime_decode.ts";
import {GOAL_OUTCOMES_SCHEMA} from "../goals/outcome_requirements.ts";
import {FULL_OUTCOME_READ_GUIDANCE, PROGRESSIVE_OUTCOME_READ_GUIDANCE} from "../work_items/interaction_contract.ts";
import {quotaSummaryReferences} from "./diagnostic_projection.ts";
import {quotaSummaryReferences, contextSourceIdentity, contextProjectionChanges,
type ProjectionSourceValues} from "./diagnostic_projection.ts";
import {encodeContextDelivery} from "./context_delivery.ts";

type Path = (string | number)[];
const at = (root: unknown, path: Path): unknown => path.reduce<unknown>((row, key) =>
Expand Down Expand Up @@ -90,3 +92,104 @@ export function progressiveContextPlan(request: JsonObject): JsonObject {
+ "No detail read or older baseline is needed. Missing target content requires recovery of this same capture before dependent work."});
return {deferred, instructions, ...(references.length ? {references} : {})};
}

const pathStartsWith = (path: Path, start: Path) => start.every((key, i) => path[i] === key);

function requiredNativeContext(request: JsonObject, sourceValues: ProjectionSourceValues):
{context: JsonObject; tokens: [Path, string][]; projected: boolean} {
const context = requireJsonObject(request.context, "current projection");
const details = requireJsonObject(request.native_projection, "supporting material reference");
const identity = contextSourceIdentity(request, request.source_number_tokens);
const plan = progressiveContextPlan({payload: context, surface: request.surface});
const deferred = plan.deferred as JsonObject[], references = (plan.references ?? []) as JsonObject[];
const instructions = plan.instructions as JsonObject[];
const at = (root: unknown, path: Path): unknown => path.reduce<unknown>((row, key) =>
row !== null && typeof row === "object" && Object.hasOwn(row, key)
? (row as Record<string | number, unknown>)[key] : undefined, root);
const tokens = (request.source_number_tokens ?? []) as [Path, string][];
const removed: Path[] = [];
let required = context;
const copies = new Map<string, JsonObject | unknown[]>();
const parent = (path: Path): Record<string | number, unknown> => {
for (let depth = 0; depth < path.length; depth++) {
const spine = path.slice(0, depth), key = JSON.stringify(spine);
if (copies.has(key)) continue;
const original = at(context, spine) as JsonObject | unknown[];
const copy = Array.isArray(original) ? [...original] : {...original};
const numbers = new Map<string, Path>();
for (const [field, value] of Object.entries(original))
if (typeof value === "number") numbers.set(field,
["context", ...spine, Array.isArray(original) ? Number(field) : field]);
if (numbers.size) sourceValues.set(copy, numbers);
copies.set(key, copy);
if (!depth) required = copy as JsonObject;
else (at(required, spine.slice(0, -1)) as Record<string | number, unknown>)[spine.at(-1)!] = copy;
}
return at(required, path.slice(0, -1)) as Record<string | number, unknown>;
};
const exactReferences = references.every(edit => identity(at(context, edit.path as Path), ["context", ...edit.path as Path])
=== identity(at(context, edit.from as Path), ["context", ...edit.from as Path]));
if (exactReferences && (deferred.length || references.length)) {
for (const edit of references) {
const path = edit.path as Path;
parent(path)[path.at(-1)!] = edit.value;
removed.push(["context", ...path]);
}
for (const edit of deferred) {
const path = edit.path as Path, owner = edit.parent as Path;
delete parent(path)[path.at(-1)!];
parent([...owner, String(edit.index_key)])[String(edit.index_key)] = "/" + owner.map(key =>
String(key).replaceAll("~", "~0").replaceAll("/", "~1")).join("/");
removed.push(["context", ...path]);
}
for (const edit of instructions) {
const path = edit.path as Path;
parent(path)[path.at(-1)!] = edit.value;
}
if (deferred.length) required.context_details = {...details,
instruction: "This view retains complete requirements and current review/check states. "
+ "Source history, review reasoning and evidence references are supporting material. "
+ "Read them from this same immutable capture when a decision, verification or review depends on them. "
+ "For source history use supporting_material_pointer plus /sources. For review detail append "
+ "/requirements/<index>/agent_review/note or /requirements/<index>/agent_review/evidence_refs instead. "
+ "Repeat --select to batch needed fields without replaying the outcome's requirements. "
+ "Detail reads never establish a full baseline. "
+ "Uncertain or missing necessary facts must be recovered before dependent action; Agent review is not independent acceptance."};
}
const remaining = tokens.filter(([path]) => !removed.some(start => pathStartsWith(path, start)));
// Unchanged source spellings cancel. Removed registered records contain only
// safe integral numbers; sparse tokens retain their original float spelling.
const adjustment = (entries: [Path, string][]) => entries.reduce((size, [path, token]) =>
path[0] === "context" ? size + token.length - JSON.stringify(at(request, path)).length : size, 0);
const projected = required !== context && Buffer.byteLength(JSON.stringify(required))
- Buffer.byteLength(JSON.stringify(context)) + adjustment(remaining) - adjustment(tokens) < 0;
return {context: projected ? required : context, tokens: projected ? remaining : tokens, projected};
}

function nativeContextContinuity(codec: JsonObject): JsonObject {
const currentTokens = (codec.source_number_tokens ?? []) as [Path, string][];
const unsafeInteger = (start: Path) => currentTokens.some(([path, token]) => pathStartsWith(path, start)
&& /^-?\d+$/.test(token) && !Number.isSafeInteger(JSON.parse(token)));
const floatSources = (start: Path) => JSON.stringify(currentTokens
.filter(([path, token]) => pathStartsWith(path, start) && /[.eE]/.test(token))
.map(([path, token]) => JSON.stringify([path.slice(start.length), token])).sort());
if (unsafeInteger(["context"]) || unsafeInteger(["baseline", "context"])
|| jsonObject(codec.baseline)?.context !== undefined
&& floatSources(["context"]) !== floatSources(["baseline", "context"])) {
return {...codec, baseline: null, retained: null, continuity: null,
source_number_tokens: currentTokens.filter(([path]) => path[0] !== "baseline")};
}
return codec;
}

/** 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[]): JsonObject {
const sourceValues: ProjectionSourceValues = new WeakMap();
const required = requiredNativeContext(request, sourceValues);
const codec = nativeContextContinuity({...request, context: required.context,
...(request.source_number_tokens === undefined ? {} : {source_number_tokens: required.tokens})});
const delivery = encodeContextDelivery(codec, legacyFields, sourceValues);
const {operation: _operation, source_edits: _edits, source_number_tokens: _numbers, ...source} = request;
return contextProjectionChanges(source, {context: required.context, delivery, projected: required.projected}, sourceValues);
}
14 changes: 6 additions & 8 deletions loopx/control_plane/quota/turn_envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def __init__(self) -> None:
raise TypeError("Use build_turn_controller_input with the original quota source")

def envelope(self) -> dict[str, Any]:
return json.loads(self.envelope_json)
envelope: dict[str, Any] = json.loads(self.envelope_json)
return envelope


def _turn_envelope_result(
Expand Down Expand Up @@ -149,7 +150,7 @@ def build_turn_controller_input(


def _build_turn_projection(
payload: Mapping[str, Any], *, scheduler_execution_context: object = None,
payload: Mapping[str, Any], *, scheduler_execution_context: Any = None,
captured_decision_path: str | None = None, operation: str = "build",
) -> dict[str, Any]:
resolution = resolve_scheduler_execution_context(scheduler_execution_context)
Expand Down Expand Up @@ -181,17 +182,12 @@ def bind_turn_decision_capture(
)


def plan_progressive_context(payload: Mapping[str, Any], *, surface: str) -> dict[str, Any]:
"""Typed presentation edits; the source JSON and all work authority stay intact."""
return _turn_envelope_result("progressive_context_plan", payload=dict(payload), surface=surface)


def prepare_context_delivery(
context: Mapping[str, Any], *, scope: Mapping[str, str], capture_id: str,
baseline: Mapping[str, Any] | None = None,
retained: Mapping[str, Any] | None = None,
continuity: Mapping[str, Any] | None = None,
surface: str = "work_context",
surface: str = "work_context", native_projection: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
"""Prepare under the Host's explicit continuity basis, not a model assertion.

Expand All @@ -206,6 +202,8 @@ def prepare_context_delivery(
baseline=dict(baseline) if baseline is not None else None,
retained=dict(retained) if retained is not None else None,
continuity=dict(continuity) if continuity is not None else None)
if native_projection is not None:
source["native_projection"] = dict(native_projection)
result = _turn_envelope_result("context_delivery", **source, source_edits=True)
return _apply_context_projection_changes(source, result)

Expand Down
7 changes: 4 additions & 3 deletions loopx/control_plane/quota/turn_envelope.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {capturedContextSelection} from "./context_selection.ts";
import {progressiveContextPlan} from "./progressive_context.ts";
import {prepareProgressiveContextDelivery} from "./progressive_context.ts";
import {contextProjectionChanges, projectQuotaDiagnostics} from "./diagnostic_projection.ts";
import type { LoopXTurnRouteValue } from "../turn_driver/turn_contract_generated.ts";
import { EffectiveAction, type QuotaEffectiveActionValue } from "./effective_action.generated.ts";
Expand Down Expand Up @@ -1408,7 +1408,6 @@ export function turnEnvelopeRoute(envelope: JsonObject, historicalText?: JsonObj
export function evaluateTurnEnvelope(value: unknown): JsonObject {
const request = requireJsonObject(value, "turn envelope request");
const operation = request.operation;
if (operation === "progressive_context_plan") return progressiveContextPlan(request);
if (operation === "replan_view") return replanView(request);
if (operation === "context_select") return capturedContextSelection(request);
if (operation === "replan_cli_view") {
Expand Down Expand Up @@ -1470,7 +1469,9 @@ export function evaluateTurnEnvelope(value: unknown): JsonObject {
return contextProjectionChanges(source, payload);
}
if (operation === "build") return buildTurnEnvelope(request);
if (operation === "context_delivery") return prepareContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS);
if (operation === "context_delivery") return request.native_projection === undefined
? prepareContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS)
: prepareProgressiveContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS);
if (operation === "native_context_lifecycle") return updateNativeContextLifecycle(request);
if (operation === "context_recovery") return restoreContextDelivery(request, LEGACY_QUOTA_CONTEXT_FIELDS);
if (operation === "bind_capture") return bindTurnDecisionCapture(request);
Expand Down
Loading
Loading