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
28 changes: 21 additions & 7 deletions docs/development/testing-and-quality.md
Original file line number Diff line number Diff line change
Expand Up @@ -1949,9 +1949,12 @@ python3 scripts/qualify-doubao-replan-semantic-action-live.py \

The model does not receive a testing-only decision schema, expected command, or
prebuilt quota packet. Clock and a small set of workspace reads are supported
for normal heartbeat preflight. Commands are parsed into a strict allowlist
without invoking a shell; quota and refresh may write only inside the temporary
fixture, and no external or repository write is allowed. The actor independently
for normal heartbeat preflight. Typed CLI commands use a strict allowlist;
Vision/stride drafts use the existing OS-isolated shell after the projected
frontier is read. Shell composition forwards only the same bounded CLI
operations. Original inputs, authority stores, host-private
data and networking remain protected. Quota and refresh may write only inside
the temporary fixture, and no external or repository write is allowed. The actor independently
qualifies the selected typed observation before executing it. Evidence-log-only,
prose-only, pre-quota, equivalent-fingerprint, and ungrounded actions fail. The
receipt retains only action kinds, command digests, and typed semantic outcomes.
Expand Down Expand Up @@ -1981,8 +1984,11 @@ progress observation 的隔离、public-safe 临时 Goal;模型接收正式 th
heartbeat task body、受限执行环境说明和 `exec_command` tool。真实 quota 必须投影 host coverage context
与最小 action packet,模型随后提交的 typed semantic delta 还要通过独立语义判定和真实
写时闸门。若模型选择新 successor,资格门要求它以当前 `obligation_id` 调用真实
`todo add`,验证 Todo 原子 receipt 与 `host_action=end_current_heartbeat`,且不得在同一
turn 执行 successor;surface/hypothesis/probe 等结果仍走真实 `refresh-state`。仅读
`todo add`,验证 Todo 原子 receipt,再由模型编写当前 evidence-linked Vision/stride,
执行原 Turn 的 bound `refresh-state` 与返回的 spend,确认一次结算后才读取新 Turn;
不得在同一 turn 执行 successor。surface/hypothesis/probe 等结果也通过同一真实
写回与结算边界。草稿复用 OS 隔离 shell,原始输入与 authority 不可改写,草稿命令
仅能组合相同的有界 CLI 操作。仅读
evidence-log、只换措辞、quota 前动作、等价指纹或未落在当前状态中的 successor 都不算
通过。receipt 只保留 action kind、command digest 与 typed semantic outcome。

Expand Down Expand Up @@ -2069,8 +2075,16 @@ The model authors its own decision; the host does not fill semantic fields or
change evidence. An observed source's evidence id and exact source reference
identify the same evidence. Checkpoint, one-spend and following-Turn verification
remain independent of shell success. Receipts disclose native-shell execution
without persisting raw conversations. Other actors retain their existing host
and seven-call contracts. Earlier bounded-grammar failures remain failed
without persisting raw conversations. The narrow semantic-action actor retains
seven ordinary calls and its separate three-read native-context allowance.
It now includes model-authored Vision/stride drafts and the original-Turn spend;
successor creation and refresh alone cannot pass. A bound writer's context
refusal can authorize its exact captured recovery reader, preserving the
original Turn. Additional recovery reads consume the ordinary budget; they
grant no extra calls. The model must reassess the changed facts and author a
new decision before retrying. Draft authoring requires the same OS isolation
prerequisite as the full journey. Other actors retain their
existing host and call limits. Earlier bounded-grammar and missing-decision failures remain failed
qualifications and are not evidence of core semantic rejection.
The other turn cases remain
bounded packet-interpretation checks. Nine core-contract scenarios cover
Expand Down
36 changes: 28 additions & 8 deletions loopx/control_plane/testing/model_tool_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,9 @@ def loopx_command_tokens(command: str) -> list[str] | None:
tokens = shlex.split(command)
except ValueError:
return None
reader_tokens = _module_reader_tokens(tokens)
if reader_tokens is not None:
return reader_tokens
for index, token in enumerate(tokens):
if Path(token).name == "loopx":
command_tokens = tokens[index:]
Expand All @@ -493,6 +496,28 @@ def loopx_command_tokens(command: str) -> list[str] | None:
return None


def _module_reader_tokens(tokens: list[str]) -> list[str] | None:
"""Normalize native readers while keeping module writes outside the host."""

if (
len(tokens) < 5
or not Path(tokens[0]).name.startswith("python")
or tokens[1] != "-m"
or tokens[2] not in {"loopx.entrypoint", "loopx.cli"}
or any(token in {";", "&&", "||", "|"} for token in tokens)
):
return None
normalized = ["loopx", *tokens[3:]]
if "quota" not in normalized:
return None
quota = normalized.index("quota")
if normalized[quota : quota + 2] not in (
["quota", "context-read"], ["quota", "replan-view"],
):
return None
return normalized


def argument_value(tokens: list[str], option: str) -> str | None:
try:
index = tokens.index(option)
Expand Down Expand Up @@ -525,7 +550,7 @@ def replan_view_command_tokens(command: str) -> list[str] | None:
The production reader is rendered through ``python -m loopx.entrypoint``
so it remains independent of the caller's installed console script. Model
behavior hosts otherwise accept only the bounded ``loopx`` console form.
Keep this exception local to the read-only replan route and return a
Share module normalization with the captured context reader and return a
console-shaped argv for the existing isolated CLI executor.
"""

Expand All @@ -538,13 +563,8 @@ def replan_view_command_tokens(command: str) -> list[str] | None:
normalized: list[str] | None = None
if tokens and Path(tokens[0]).name == "loopx":
normalized = list(tokens)
elif (
len(tokens) >= 5
and tokens[1] == "-m"
and tokens[2] in {"loopx.entrypoint", "loopx.cli"}
and Path(tokens[0]).name.startswith("python")
):
normalized = ["loopx", *tokens[3:]]
else:
normalized = _module_reader_tokens(tokens)
if normalized is None or "quota" not in normalized:
return None
quota = normalized.index("quota")
Expand Down
Loading
Loading