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
44 changes: 44 additions & 0 deletions docs/reference/required-work-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,50 @@ journal,防止短暂 IO 故障恢复后复活旧基线。不可变 capture 与
重新生成。此开关不重启停用 Goal、不改变额度或 lease;正常 Desktop、全部 Goal 的实际
采用和真实 solver 完整成本及效果仍须分别验收。

### Selected capture details / 按需读取捕获详情

`quota context-read --select /path --select /another/path` reads an explicit
batch of RFC 6901 JSON pointers from the original digest-bound capture. Supply
the existing `--capture-file` and `--capture-sha256` reference, or start through
the existing session lookup. Omit `--select` for unchanged full-capture reads.
Selection is JSON-only; `~1` escapes `/`, `~0` escapes `~`, and the empty pointer
selects the root. Missing pointers or invalid array indices reject the whole
batch; a present JSON `null` is a valid value. Duplicate pointers are removed
and the remaining pointers are sorted.

The existing page envelope keeps the original `sha256` and adds
`selected_pointers` and `selection_sha256`. Concatenated `content` is a
`context_capture_selection_v1` object with `selections: [{pointer, value}]`.
Its offsets, total bytes and EOF describe only that derived view. Follow
`next_command`, which retains both hashes and the exact selection; a nonzero
offset requires `--selection-sha256`. The reader verifies both hashes, preserves
original JSON values (including large integers), and performs no admission,
settlement or live Goal lookup. Selection requires the existing typed runtime;
ordinary full reads retain their file-only path.

Selected EOF, including a root selection, cannot establish a full-context
baseline in the native hook. This shared reader enables exact detail expansion;
it does not change which facts a caller must read, relax required reads, or
switch existing native input to a necessary-only view. Those consumers must
integrate their scope, instructions and recovery together before claiming a
progressive-delivery or total-cost improvement.

`quota context-read --select /path --select /another/path` 用 RFC 6901 JSON
pointer 从原始摘要绑定的 capture 批量读取详情。提供已有 `--capture-file` 与
`--capture-sha256`,或从已有会话查询入口开始;省略 `--select` 保持原有全量读取。
选择模式仅支持 JSON:`~1` 表示 `/`,`~0` 表示 `~`,空 pointer 选择根对象。
任一路径不存在或数组索引无效时整批失败;已存在的 `null` 合法。路径去重并排序。

分页保留原始 `sha256`,新增 `selected_pointers` 与 `selection_sha256`。
拼接 `content` 后得到带 `selections: [{pointer, value}]` 的
`context_capture_selection_v1`;偏移、总字节数与 EOF 仅描述此派生视图。
沿 `next_command` 继续,它保留两个摘要及精确路径;非零偏移必须携带
`--selection-sha256`。读取器校验两者,保留包括大整数在内的原 JSON 值,
不准入、不结算、不查询实时 Goal。选择模式依赖已有 typed runtime;普通全量
读取仍只走文件路径。包括根对象选择在内的详情 EOF 均不能被原生 hook 记为
完整上下文基线。此共享读取器提供精确展开能力,不改变调用方必读事实或自动切换
必要视图;调用方须共同接入范围、指引与恢复后,再验收渐进投递及完整成本收益。

### Saved context cache / 已保存上下文缓存

For `quota context-project`, enabling context delivery no longer requires a
Expand Down
53 changes: 44 additions & 9 deletions loopx/cli_commands/quota_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from typing import Any

from ..control_plane.content_digest import BARE_SHA256_PATTERN
from ..control_plane.effect_runtime import EffectRuntimeRejected
from ..control_plane.effect_runtime import EffectRuntimeRejected, EffectRuntimeRemoteError, EffectRuntimeStartupError
from ..control_plane.quota.error_codes import QuotaCommandValidationError, QuotaDecisionTransportError
from ..control_plane.turn_driver.decision_capture import (
validate_handoff_socket, CONTEXT_CAPTURE_READ_GUIDANCE, CONTEXT_CAPTURE_PAGE_GUIDANCE, context_capture_read_command,
Expand All @@ -37,7 +37,9 @@ def _capture_page(path: Path, raw: bytes, digest: str, *, offset: int, size: int
output_limit: int | None, instruction: bool,
presentation: dict[str, Any] | None = None,
next_page_bytes: int | None = None,
next_page_output_bytes: int | None = None) -> dict[str, Any]:
next_page_output_bytes: int | None = None,
select: list[str] | None = None,
selection_sha256: str | None = None) -> dict[str, Any]:
end = min(offset + size, len(raw))
while end < len(raw) and raw[end] & 0xC0 == 0x80:
end -= 1
Expand All @@ -48,11 +50,17 @@ def page(text: str) -> dict[str, Any]:
result: dict[str, Any] = {**(presentation or {}), "ok": True, "schema_version": "context_capture_page_v1", "sha256": digest,
"offset": offset, "next_offset": cursor if cursor < len(raw) else None,
"total_bytes": len(raw), "eof": cursor == len(raw), "content": text}
if instruction:
if select is not None:
result.update(selection_sha256=selection_sha256, selected_pointers=select)
result["read_instruction"] = ("This is selected detail from the original capture, not a complete context read. "
"Offsets and EOF describe only this selected view. Follow next_command to finish it; "
"keep both digests. Detail delivery does not establish a full-context baseline or grant authority.")
elif instruction:
result["read_instruction"] = CONTEXT_CAPTURE_PAGE_GUIDANCE
if cursor < len(raw):
result["next_command"] = context_capture_read_command(path, digest, offset=cursor, page_bytes=next_page_bytes or size,
max_page_output_bytes=next_page_output_bytes or output_limit)
max_page_output_bytes=next_page_output_bytes or output_limit,
select=select, selection_sha256=selection_sha256)
return result

def fits(result: dict[str, Any]) -> bool:
Expand Down Expand Up @@ -85,7 +93,8 @@ def read_context_capture(args: argparse.Namespace, *, presentation: dict[str, An
"""Bounded local IO over immutable JSON, not a new context/authority owner.

Verify the complete byte digest on every page. The cursor addresses bytes
in that exact capture, so a replacement cannot silently mix two decisions.
in that capture or its separately digest-bound selection, so a replacement
cannot silently mix two decisions or detail views.
EOF describes this page, never a model's complete-consumption receipt.
"""
try:
Expand Down Expand Up @@ -124,18 +133,44 @@ def read_context_capture(args: argparse.Namespace, *, presentation: dict[str, An
if type(offset) is not int or offset < 0 or type(size) is not int or not 256 <= size <= maximum:
raise ValueError("context-read requires a nonnegative byte offset and page-bytes between 256 and 8192; "
"larger pages require a sufficient max-page-output-bytes within the capture limit")
path, raw, _ = _read_capture(value, digest)
path, raw, payload = _read_capture(value, digest)
selectors = getattr(args, "select", None)
selection_digest = getattr(args, "selection_sha256", None)
if selectors is None and selection_digest is not None:
raise ValueError("selection-sha256 requires --select")
if selectors is not None:
if getattr(args, "format", None) != "json":
raise ValueError("selected context-read requires JSON output")
if offset > 0 and selection_digest is None:
raise ValueError("selected continuation requires selection-sha256; follow next_command")
from ..control_plane.quota.turn_envelope import _turn_envelope_result

plan = _turn_envelope_result("context_select", context=payload, select=selectors)
views: list[dict[str, Any]] = []
for item in plan["selections"]:
selected: Any = payload
for key in item["path"]:
selected = selected[key]
views.append({"pointer": item["pointer"], "value": selected})
raw = json.dumps({"schema_version": "context_capture_selection_v1", "selections": views},
ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + b"\n"
expected = hashlib.sha256(raw).hexdigest()
if selection_digest is not None and selection_digest != expected:
raise ValueError("selected view digest changed; restore the original selection and reader")
selection_digest = expected
selectors = [item["pointer"] for item in views]
if offset > len(raw) or (offset < len(raw) and raw[offset] & 0xC0 == 0x80):
raise ValueError("capture offset is outside the file or not a UTF-8 boundary; use the returned next offset")
return _capture_page(path, raw, digest, offset=offset, size=size,
output_limit=output_limit, instruction=bool(context_dir),
presentation=presentation, next_page_bytes=next_page_bytes,
next_page_output_bytes=next_page_output_bytes)
except (OSError, ValueError, RecursionError) as exc:
next_page_output_bytes=next_page_output_bytes, select=selectors, selection_sha256=selection_digest)
except (OSError, ValueError, RecursionError, EffectRuntimeRemoteError, EffectRuntimeStartupError) as exc:
# Recovery is presentation-only: keep failures separate from quota's
# collection error path and retain the existing receipt/capture.
return {"ok": False, "schema_version": "context_capture_page_v1",
"reason": str(exc) if isinstance(exc, ValueError) else "original capture could not be read",
"reason": str(exc) if isinstance(exc, (ValueError, EffectRuntimeRemoteError, EffectRuntimeStartupError))
else "original capture could not be read",
"recovery": "Restore the original digest-bound capture; no guard, admission or spend was performed."}


Expand Down
5 changes: 5 additions & 0 deletions loopx/cli_commands/quota_registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ def register_quota_command(
"Use for shared Codex App hook configuration; cannot combine with --codex-context-dir. "
"context-config renders native configuration for the exact Goal/Agent; no host configuration, trust, guard or spend are changed.")
quota_parser.add_argument("--capture-sha256", help="For context-read/context-project: exact SHA-256 supplied with the capture reference.")
quota_parser.add_argument("--select", action="append",
help="For context-read: repeat an exact RFC 6901 JSON pointer to read a batch from the original capture. "
"Selected detail pages never acknowledge full-context retention; omit for unchanged full capture reads.")
quota_parser.add_argument("--selection-sha256",
help="For selected context-read continuations: exact derived-view digest from the preceding page.")
quota_parser.add_argument("--offset", type=int, default=0, help="For context-read: byte offset returned by the previous page.")
quota_parser.add_argument("--page-bytes", type=int,
help="For context-read: maximum UTF-8 content bytes per page (JSON default: 64000 under a 64000-byte stdout budget; Markdown: 4096). "
Expand Down
34 changes: 34 additions & 0 deletions loopx/control_plane/quota/context_selection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/** Pure RFC 6901 selection over one captured JSON object. No state or admission. */
import {requireJsonObject} from "../runtime_decode.ts";
import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts";
import type {JsonObject} from "../effect_program.ts";

export function capturedContextSelection(request: JsonObject): JsonObject {
const context = requireJsonObject(request.context, "captured context");
const selectors = request.select;
if (!Array.isArray(selectors) || !selectors.length || selectors.some(value => typeof value !== "string"))
throw new EffectRuntimeRequestError("context selection requires a nonempty array of JSON pointers");
const pointers = [...new Set(selectors as string[])].sort();
const selections = pointers.map(pointer => {
if (pointer !== "" && !pointer.startsWith("/") || /~(?:[^01]|$)/.test(pointer))
throw new EffectRuntimeRequestError("context selection requires RFC 6901 pointers");
const tokens = pointer === "" ? [] : pointer.slice(1).split("/").map(token => token.replace(/~1/g, "/").replace(/~0/g, "~"));
const path: (string | number)[] = [];
let value: unknown = context;
for (const token of tokens) {
if (Array.isArray(value)) {
if (!/^(0|[1-9][0-9]*)$/.test(token) || !Number.isSafeInteger(Number(token)) || Number(token) >= value.length)
throw new EffectRuntimeRequestError("context selection array index is missing or invalid");
path.push(Number(token));
value = value[Number(token)];
} else if (value !== null && typeof value === "object" && Object.hasOwn(value, token)) {
path.push(token);
value = (value as JsonObject)[token];
} else throw new EffectRuntimeRequestError("context selection pointer does not exist");
}
// Return the verified address, not a JS-number-coerced copy of the value.
// The file adapter retains original JSON values and owns byte serialization.
return {pointer, path};
});
return {selections};
}
2 changes: 2 additions & 0 deletions loopx/control_plane/quota/turn_envelope.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {capturedContextSelection} from "./context_selection.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 @@ -1407,6 +1408,7 @@ export function evaluateTurnEnvelope(value: unknown): JsonObject {
const request = requireJsonObject(value, "turn envelope request");
const operation = request.operation;
if (operation === "replan_view") return replanView(request);
if (operation === "context_select") return capturedContextSelection(request);
if (operation === "replan_cli_view") {
const payload = requireJsonObject(request.payload, "replan CLI payload");
const reads = replanReadCommands(payload);
Expand Down
9 changes: 7 additions & 2 deletions loopx/control_plane/turn_driver/decision_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ def native_context_page_budget() -> tuple[int, int]:

def context_capture_read_command(path: Path, digest: str, *, offset: int = 0,
page_bytes: int | None = None,
max_page_output_bytes: int | None = None) -> str:
max_page_output_bytes: int | None = None,
select: list[str] | None = None,
selection_sha256: str | None = None) -> str:
from ...command_invocation import loopx_cli_argv

if page_bytes is None:
Expand All @@ -195,6 +197,8 @@ def context_capture_read_command(path: Path, digest: str, *, offset: int = 0,
return shlex.join([*loopx_cli_argv(), "--format", "json", "quota", "context-read",
"--capture-file", str(path), "--capture-sha256", digest,
"--offset", str(offset), "--page-bytes", str(page_bytes),
*(argument for pointer in select or [] for argument in ("--select", pointer)),
*(["--selection-sha256", selection_sha256] if selection_sha256 is not None else []),
*(["--max-page-output-bytes", str(max_page_output_bytes)] if max_page_output_bytes is not None else [])])


Expand Down Expand Up @@ -228,7 +232,8 @@ def read_context_capture_file(value: str, digest: str, *, private: bool = False)

def capture_page_range(page: dict[str, Any], *, raw: bytes, digest: str) -> tuple[int, int] | None:
"""Verify reported page bytes, independently of a host's retention claim."""
if page.get("schema_version") != "context_capture_page_v1" or page.get("sha256") != digest:
if ("selection_sha256" in page or "selected_pointers" in page
or page.get("schema_version") != "context_capture_page_v1" or page.get("sha256") != digest):
return None
start, content = page.get("offset"), page.get("content")
if type(start) is not int or start < 0 or not isinstance(content, str) or page.get("ok") is not True:
Expand Down
Loading
Loading