Skip to content
Closed
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 @@ -312,15 +312,18 @@ if total_usage > GOAL_VISION_TOTAL_LIMIT:
第二份 transcript。`examples/project/goal-vision-refresh-state-budget-smoke.py`
同时验证写入正确性、预算与 repair delta。

当 material refresh 没有 vision patch 或 unchanged reason 时,CLI 会产生 `vision_checkpoint_missing` acceptance gap。Todo 即使完成,也不能直接 terminal。
正常调用应当在同一次写回里带上这个决定。当 agent lane 的 material closeout 没有 vision
patch 或 unchanged reason 时,CLI 会记录 segment 并把 checkpoint 标成
`vision_checkpoint_missing`,同时返回一条可直接执行的修复命令:在原 turn 用它补齐决定,
settlement identity 不变、不会再花一次 spend。补齐之前,Todo 即使完成也不能宣告 terminal。

修复选择有三种:
要满足这个决定,路径仍是三种:

1. 写一个 bounded vision patch;
2. 明确记录 vision unchanged reason;
3. 用 evidence 关闭或 supersede 该 vision frontier。

不要为了消除 warning 随便写“unchanged”。Reason 必须与本轮 acceptance 事实一致。
不要为了消除这个提示自动填一个空泛的“unchanged”。Reason 必须与本轮 acceptance 事实一致。

## Signal、Anchor 与 Feedback 不直接变成 Todo Truth

Expand Down
13 changes: 8 additions & 5 deletions docs/reference/contracts/interface-budget-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and size/count budgets.

| Surface | Owner | Consumer Action | Cold Path | Size Budget | Nested Budget | Count Budget |
| --- | --- | --- | --- | --- | --- | --- |
| `heartbeat_prompt_json` | heartbeat automation | wake and route one bounded turn | `quota should-run`, `status`, or `review-packet --handoff-only` | `json_chars <= 4800` plus `interface_budget.within_budget=true` | `nested_keys <= 40` | `top_level_keys <= 30` |
| `heartbeat_prompt_json` | heartbeat automation | wake and route one bounded turn | `quota should-run`, `status`, or `review-packet --handoff-only` | `json_chars <= 5300` plus `interface_budget.within_budget=true` | `nested_keys <= 40` | `top_level_keys <= 30` |
| `review_packet_handoff_only_json` | project-agent handoff | forward the smallest sufficient task packet | full `review-packet` or run-history artifact | `json_chars <= 3000` plus `handoff_interface_budget.within_budget=true` | `nested_keys <= 40` | `top_level_keys <= 18` |
| `quota_should_run_json` | quota guard | decide whether the selected goal may spend compute | `status`, `history`, or active state | `json_chars <= 14000` | `nested_keys <= 350` | `top_level_keys <= 52` |
| `dashboard_status_json` | operator dashboard | render first-screen operator state | `history`, run artifacts, or project-local adapter output | `json_chars <= 19500` | `nested_keys <= 260` | `top_level_keys <= 25` |
Expand Down Expand Up @@ -36,10 +36,13 @@ for the richer generator packet; neither is the recurring Agent hot path.
The heartbeat envelope ceiling covers the unbound and representative agent/scope-bound
Codex App thin fixtures. It includes generator metadata and repeated bound commands,
not only the execution prompt. The shared host contract added static safety, repair
routing, and retry-stable Turn initialization; the scoped fixture now uses about
4,362 JSON characters. The 4,800-character ceiling leaves roughly 10% headroom for
that fixture, without relaxing the independent **2,500-character thin task body**,
4,000-character native Goal body, structural limits, or emitted CLI ceilings.
routing, and retry-stable Turn initialization, and the vision writeback guidance now
names the material-closeout vision decision and its same-turn repair. The scoped
fixture now uses about 4,795 JSON characters, so the ceiling moves to 5,300 to keep the
roughly 10% headroom this contract asks for, rather than squeezing writeback semantics
out of the envelope. Nothing else is relaxed: the independent **2,500-character thin
task body**, 4,000-character native Goal body, structural limits, and emitted CLI
ceilings are unchanged.
It is not a token count, execution quota, or allowance to append more instructions.
Arbitrary-length caller paths/scopes are not promised to fit this fixed fixture
envelope; their emitted output is qualified separately by the CLI matrix.
Expand Down
16 changes: 16 additions & 0 deletions docs/reference/protocols/goal-vision-replan-contract-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,22 @@ Valid checkpoint decisions are:
- `not_required`: no material closeout trigger was present, including a valid
typed in-flight continuation.

The closeout is where this requirement is met, so the writeback guidance every
wake receives states it: a material closeout -- a material `delivery_outcome` on
the agent lane, or a durable `## Next Action` update -- carries its own vision
decision in the same `refresh-state` call, and does not record an `unchanged`
reason the segment did not earn.

An omission is recorded, not discarded, and is repaired in the same turn:
`refresh-state` writes the segment with a `missing_required` checkpoint and
returns the repair command that repeats the original invocation with the missing
decision. The goal cannot reach its terminal no-follow-up state until that
decision satisfies the checkpoint, and the same-turn supplement applies it to the
original settlement identity rather than to a new one, so the repair neither
re-authors the segment nor spends a second time. An in-flight continuation owes
no decision, and neither does a closeout that carries no `agent_id`, because a
per-agent decision requires an agent to make it.

`missing_required` is not a chat reminder. Status keeps it in compact run
history, quota filters it by current `agent_id`, and goal-frontier projection
turns it into `acceptance_gaps[]`. If the current agent has no runnable
Expand Down
3 changes: 3 additions & 0 deletions examples/control_plane/cli-output-probe-runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def _receipt_row(
"reward_memory_outcome_prompt_revision": (
semantics.reward_memory_outcome_prompt_revision(text)
),
"vision_writeback_decision_prompt_revision": (
semantics.vision_writeback_decision_prompt_revision(text)
),
"managed_executor_binding_revision": (
semantics.managed_executor_binding_revision(text)
),
Expand Down
12 changes: 10 additions & 2 deletions examples/control_plane/heartbeat-prompt-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ def user_output_policy(task_body: str, *, mode: str) -> dict[str, str]:
def assert_sole_notification_authority(task_body: str, *, mode: str) -> None:
body = normalized(task_body)
assert "no-change=`surface_only`/no spend; unchanged->" in body, mode
assert "`--vision-unchanged-reason`; material->actual outcome." in body, mode
assert (
"`--vision-unchanged-reason`; material closeout->actual outcome"
"+自己的 vision 决定; 缺则按返回的修复命令同 turn 补齐, 补齐前不 terminal; "
"勿自动填 unchanged."
) in body, mode

if mode == "full":
assert (
Expand Down Expand Up @@ -714,7 +718,11 @@ def main() -> int:
):
assert "no-change=`surface_only`/no spend" in task, label
assert "`--vision-unchanged-reason`" in task, label
assert "material->actual outcome" in task, label
assert "material closeout->actual outcome" in task, label
assert "自己的 vision 决定" in task, label
assert "缺则按返回的修复命令同 turn 补齐" in task, label
assert "补齐前不 terminal" in task, label
assert "勿自动填 unchanged" in task, label
assert "if absent say" not in thin_task, thin_task
assert "If false/0: quiet/no-user-todo" not in thin_task, thin_task

Expand Down
5 changes: 4 additions & 1 deletion examples/control_plane/hot-path-interface-budget-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
"cold_path": "quota should-run, status, or review-packet --handoff-only",
# Includes generator metadata and scoped commands, not just task_body.
# The independent 2,500-character thin body cap remains unchanged.
"max_json_chars": 4_800,
# The representative scoped fixture reached 4,795 characters, so this
# ceiling restores the contract's ~10% margin instead of squeezing the
# vision writeback guidance out of the envelope to fit 4,800.
"max_json_chars": 5_300,
"max_nested_keys": 40,
"max_top_level_keys": 30,
"budget_field": "interface_budget",
Expand Down
4 changes: 3 additions & 1 deletion loopx/control_plane/heartbeat/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
)
HEARTBEAT_VISION_WRITEBACK_RULE_SHORT = (
"writeback: no-change=`surface_only`/no spend; "
"unchanged->`--vision-unchanged-reason`; material->actual outcome."
"unchanged->`--vision-unchanged-reason`; material closeout->actual outcome"
"+自己的 vision 决定; 缺则按返回的修复命令同 turn 补齐, 补齐前不 terminal; "
"勿自动填 unchanged."
)
REWARD_MEMORY_OUTCOME_RULE = (
"`reward_memory_recall.experiment.automatic_ingest=true`: reusable Todo outcomes "
Expand Down
44 changes: 44 additions & 0 deletions loopx/control_plane/testing/cli_output_differential.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ class GrowthAllowance:
"compact_payload_chars": 640,
}

# The vision writeback guidance now states the material-closeout decision: the
# closeout carries its own vision decision, an omission is repaired on the same
# turn through the command the CLI returns, the goal cannot reach its terminal
# state first, and a segment does not record an unchanged reason it did not
# earn. The allowance is bound to an exact none-to-v1 prompt revision, applies
# only to heartbeat rows, and keeps the absolute surface ceilings intact. Once
# v1 is the baseline, normal hot-path budgets apply again.
_VISION_WRITEBACK_DECISION_PROMPT_V1_MIGRATION_ALLOWANCE: dict[Metric, int] = {
"chars": 128,
"utf8_bytes": 384,
"lines": 2,
"compact_payload_chars": 128,
}

# Two reviewed causes grow the Turn plan readback once, and both are consequences
# of the same declared behavior change:
#
Expand Down Expand Up @@ -249,6 +263,30 @@ def _reward_memory_outcome_prompt_allowance(
return 0


def _vision_writeback_decision_prompt_allowance(
row_id: str,
base: Mapping[str, Any],
candidate: Mapping[str, Any],
metric: Metric,
) -> int:
surface = row_id.partition("/")[2].partition("/")[0]
if (
row_id.startswith(("surface/", "variant/"))
and surface
in {
"heartbeat_prompt_thin",
"heartbeat_prompt_brief",
"heartbeat_prompt_compact",
"heartbeat_prompt_full",
}
and base.get("vision_writeback_decision_prompt_revision") is None
and candidate.get("vision_writeback_decision_prompt_revision")
== "vision_writeback_decision_prompt_v1"
):
return _VISION_WRITEBACK_DECISION_PROMPT_V1_MIGRATION_ALLOWANCE[metric]
return 0


# loopx_guided_todo_delta_v0 adds the continuation-aware Todo authoring
# decision contract (reuse/update/link_successor/add_new plus a bounded
# runnable-frontier summary) to the guided start-goal packet when an
Expand Down Expand Up @@ -585,6 +623,12 @@ def _compare_row(base: dict[str, Any], candidate: dict[str, Any]) -> dict[str, A
candidate,
metric,
),
_vision_writeback_decision_prompt_allowance(
row_id,
base,
candidate,
metric,
),
_turn_host_and_managed_executor_binding_allowance(
row_id,
base,
Expand Down
24 changes: 24 additions & 0 deletions loopx/control_plane/testing/cli_output_semantics.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,30 @@ def managed_executor_binding_revision(text: str) -> str | None:
else None
)


def vision_writeback_decision_prompt_revision(text: str) -> str | None:
"""Attribute the one-time material-closeout vision writeback revision.

This is qualification evidence for the exact guidance sentence, never a
runtime classifier: it requires the decision, the original-turn repair
through the command the CLI returns, the terminal gate, and the ban on
fabricating an unchanged reason, so a partial or paraphrased prompt cannot
claim the one-time allowance.
"""

required = (
"material closeout->actual outcome",
"自己的 vision 决定",
"按返回的修复命令同 turn 补齐",
"补齐前不 terminal",
"勿自动填 unchanged",
)
return (
"vision_writeback_decision_prompt_v1"
if all(fragment in text for fragment in required)
else None
)

_MARKDOWN_HEADING = re.compile(r"^#{1,6}\s+.+$")
_RUNTIME_ROOT_COMMAND_ROUTE = re.compile(
r"(?m)(?:^|[\"'`])[^\r\n\S]*loopx\s+--runtime-root\s+"
Expand Down
93 changes: 93 additions & 0 deletions tests/control_plane/test_cli_output_differential.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,99 @@ def test_reward_memory_outcome_prompt_budget_is_one_time_bounded_and_prompt_only
assert _compare_row(other, {**current, "row_id": other["row_id"]})["failures"]


@pytest.mark.parametrize("row_kind", ["surface", "variant"])
@pytest.mark.parametrize("mode", ["thin", "brief", "compact", "full"])
def test_vision_writeback_decision_prompt_allowance_is_exact_and_prompt_only(
row_kind, mode
):
from loopx.control_plane.heartbeat.rules import (
HEARTBEAT_VISION_WRITEBACK_RULE_SHORT,
)
from loopx.control_plane.testing.cli_output_differential import (
_VISION_WRITEBACK_DECISION_PROMPT_V1_MIGRATION_ALLOWANCE as ALLOWANCE,
_compare_row,
_vision_writeback_decision_prompt_allowance,
)
from loopx.control_plane.testing.cli_output_semantics import (
vision_writeback_decision_prompt_revision,
)

# The revision claims the one-time allowance only for the whole sentence.
assert (
vision_writeback_decision_prompt_revision(HEARTBEAT_VISION_WRITEBACK_RULE_SHORT)
== "vision_writeback_decision_prompt_v1"
)
assert (
vision_writeback_decision_prompt_revision(
HEARTBEAT_VISION_WRITEBACK_RULE_SHORT.replace(
"勿自动填 unchanged", "best effort"
)
)
is None
)

row_id = f"{row_kind}/heartbeat_prompt_{mode}/small/json"
unbound = _row(row_id=row_id)
migrated = {
**unbound,
"vision_writeback_decision_prompt_revision": (
"vision_writeback_decision_prompt_v1"
),
}
for metric in ("chars", "utf8_bytes", "compact_payload_chars", "lines"):
assert (
_vision_writeback_decision_prompt_allowance(row_id, unbound, migrated, metric)
== ALLOWANCE[metric]
)
# An already-migrated baseline receives nothing.
assert (
_vision_writeback_decision_prompt_allowance(row_id, migrated, migrated, "chars")
== 0
)
# No other surface qualifies, even with the revision present.
other_id = f"{row_kind}/status/small/json"
assert (
_vision_writeback_decision_prompt_allowance(
other_id,
_row(row_id=other_id),
{
**_row(row_id=other_id),
"vision_writeback_decision_prompt_revision": (
"vision_writeback_decision_prompt_v1"
),
},
"chars",
)
== 0
)
# On the tight hot-path surface the migration allowance is the whole budget.
thin_id = f"{row_kind}/heartbeat_prompt_thin/small/markdown"
# The real thin Markdown row is a few thousand characters, which is why its
# ratio allowance stays at the 32-character floor.
base = _row(
row_id=thin_id,
format="markdown",
chars=6_400,
utf8_bytes=6_400,
lines=200,
)
already_migrated = {
**base,
"vision_writeback_decision_prompt_revision": (
"vision_writeback_decision_prompt_v1"
),
}
current = {
**already_migrated,
"chars": base["chars"] + ALLOWANCE["chars"],
}
assert not _compare_row(base, current)["failures"]
assert _compare_row(base, {**current, "chars": current["chars"] + 1})["failures"]
assert _compare_row(
already_migrated, {**current, "chars": current["chars"] + 129}
)["failures"]


def test_managed_executor_binding_budget_is_one_time_bounded_and_turn_only() -> None:
from loopx.control_plane.testing.cli_output_differential import (
_TURN_HOST_AND_MANAGED_EXECUTOR_BINDING_V0_GROWTH_ALLOWANCE as ALLOWANCE,
Expand Down
5 changes: 4 additions & 1 deletion tests/control_plane/test_heartbeat_prompt_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ def test_heartbeat_envelope_and_body_overflow_are_both_rejected() -> None:
check("heartbeat_prompt_json", payload)
# Passing the inner body check cannot hide extra envelope metadata.
envelope = {**payload, "extra": ""}
envelope["extra"] = "x" * (4800 - smoke["json_size"](envelope))
ceiling = int(
smoke["SURFACE_BUDGETS"]["heartbeat_prompt_json"]["max_json_chars"]
)
envelope["extra"] = "x" * (ceiling - smoke["json_size"](envelope))
check("heartbeat_prompt_json", envelope)
envelope["extra"] += "x"
with pytest.raises(AssertionError):
Expand Down