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
30 changes: 30 additions & 0 deletions docs/architecture/rfcs/agent-loop-effect-interpreter-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,36 @@ and reject signature equivalence after a step/order/judge mutation. The existing
This closes that transport omission, not the three convergence acceptances or
permission to retire their active Python/host adapters.

Following the capsule through the actual shared Host input exposed a second
loss: DSH/cloud retained only its unavailable-context field, so a valid signed
audit never reached the provider. That adapter now forwards the existing audit
and original capture route/hash. Real File/SQLite normal/replan captures through
a protocol-only DSH subprocess cover ordered obligations, the continuing judge,
insufficient-proof conditions, signature refusal and unchanged source/state.
The same audit also found work qualification missing: the capsule filtered out
outcome-floor/degradation rules, and that Host omitted the whole existing
execution profile. The typed projection now retains it intact and the Host
forwards it. A new material result carrying that profile returns typed,
public-safe `work_evidence` and `validation_evidence` arrays and preserves its
actual delivery scale/outcome; the outer executor validates and retains both.
Native source oracles and signed mutations cover these rules. Inputs without
either contract retain their prior prompt, and older journal replay remains
readable. The outer executor keeps all state/quota/scheduler effects. This
qualifies clause delivery at that caller, not model adherence, installed
adoption or complete-cost improvement. Count the cost of restored requirements;
the earlier incomplete input is not an accepted efficiency baseline.

The complete execution profile adds 138–142 JSON characters/UTF-8 bytes and no
lines to ordinary Turn plans on the unchanged 96-row public CLI fixture. The
original base/head differential fails its 64/70-character and 128-byte growth
allowances; this remains a recorded failure. Qualify the restored outcome-floor
and degradation fields with a 160-character/byte one-time allowance only when
the old four-field profile becomes complete, with unchanged signature coverage
and no other JSON-shape change. Already complete, partial or unrelated changes
receive no allowance. Keep absolute budgets, line limits, fixture populations,
complete source/Host clause checks and the production 8 KiB diagnostic. This
explains the required presentation cost, not model tokens or efficiency gains.

中文:本批修复验收步骤、继续说明和当前判断在 capsule 中丢失的问题;签名一致曾只
比较已经裁剪过的投影。File/SQLite 同次捕获验证完整条款和篡改后的签名差异;
8 KiB 警告线保留。安装态 host、模型效果及整体统一仍按原三个 Todo 验收。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,26 @@ candidate 适配器也传递此前遗漏的签名限制,原范围/workspace
必需文本可以超出并警告。这只关闭传输遗漏,不代表三项收敛验收通过,也不授权退役仍
活跃的 Python/host adapter。

沿 capsule 追到实际共享 Host 输入又暴露一处丢失:DSH/云端只传递其中的
unavailable-context 字段,签名有效的 audit 没有到达 provider。适配器现传递既有
audit 及原捕获路由/哈希。真实 File/SQLite 普通工作与 replan 捕获通过无模型 DSH
子进程验证有序义务、继续执行的 judge、非证明条件、签名拒绝及来源/状态未变。
同次核对还发现工作资格丢失:capsule 过滤了成果底线/退化规则,该 Host 又遗漏整份
既有 execution profile。typed 投影现完整保留,Host 直接传递。携带该 profile 的新
material result 返回公开安全的 typed `work_evidence` 与 `validation_evidence`
数组,并保留实际 delivery scale/outcome;外层 executor 验证并保留两组证据及分类。
原生来源 oracle 和签名篡改覆盖这些规则。两种契约都不存在的 prompt 保持原输入,旧
journal replay 保持可读;所有状态、quota 和 scheduler 效果仍归外层 executor。本批
只验证该调用方的条款交付,不证明模型遵守、安装采用或完整成本收益。应计入恢复要求
的成本;旧的不完整输入不能作为已验收的效率基线。

完整 execution profile 在原有 96 行公开 CLI 夹具中,令普通 Turn plan 增加
138–142 个 JSON 字符/UTF-8 字节,不增加行数。原 base/head 差分超过 64/70
字符和 128 字节增长额度,仍按原失败保留。仅当旧四字段 profile 补齐成果底线和
退化字段、签名覆盖不变且无其他 JSON 结构变化时,给予一次性 160 字符/字节额度。
已经完整、只补部分或无关变化均不适用。绝对预算、行数、夹具人口、完整来源/Host
条款验证及生产 8 KiB 诊断保持。本结果说明必要展示成本,不代表模型 token 或效率收益。

统一工作上下文现在由既有 TS interaction owner 选择并满足必读,注册的 Python
adapter 核验来源,在普通 heartbeat/quota 与签名 envelope 中返回完整当前任务来源,
无需启用 TurnEnvelope。混合 Goal 文件仍必须经渐进式路径全文读取,不能按历史标题
Expand Down
31 changes: 31 additions & 0 deletions docs/reference/protocols/turn-envelope-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,41 @@ captured full decision. Historical saved envelopes retain their original
signatures; this projection change grants no closure or execution authority.
The 8 KiB warning remains diagnostic when required meaning exceeds it.

Shared DSH/cloud Host input forwards this audit from the signed capsule, with
the original capture route and hash for additional acceptance/coverage evidence.
Hosts read its ordered obligations and current judge before proposing closure;
they return a semantic result while the outer Turn executor owns state, quota
and scheduler effects. The same input carries the complete existing
`execution_profile`, including outcome-floor and degradation rules, to size and
qualify work. Display filtering must not discard those clauses or ordered
requirements before signing. A new material result carrying `execution_profile`
must return `work_evidence` and `validation_evidence` as separate arrays of
1-8 non-empty public-safe strings, at most 240 characters per item and 1,200
characters per field. The Host must preserve its actual
`delivery_batch_scale` and `delivery_outcome` rather than replacing them with a
fixed progress class. The outer Turn executor validates and retains this evidence
in the typed host result; `turn run-once` projects it as `host_evidence`.
Legacy journal replay may retain an older material result without these fields.
Stop results omit both arrays. The profile does not transfer writeback or quota
ownership. Historical profiles keep their original signature, and inputs without
either contract retain their prompt.

中文:验收 capsule 完整保留原来源的有序步骤、继续执行说明、当前结束/继续判断、
认可的证据种类和不能作为证明的条件;展示裁剪不能丢掉条款或列表尾部。新投影使用
既有签名维度,历史 envelope 的签名不重写。详细验收记录仍从同次捕获读取,体积
超过 8 KiB 时保留 warning,不以缩短义务或放大预算替代语义验收。
共享 DSH/云端 Host 输入也从签名 capsule 传递这份 audit,附原捕获路由与哈希,供
补读验收/覆盖证据;提议收尾前须读取有序义务及当前 judge。Host 返回语义结果,
状态、quota 和 scheduler 效果仍由外层 Turn executor 执行。同一输入也传递完整的既有
`execution_profile`,包括成果底线、退化规则和有序要求,供确定工作规模及验收;不能
在签名前按展示字段丢弃。携带 `execution_profile` 的新 material result 必须分别返回
`work_evidence` 与 `validation_evidence` 字符串数组:各 1-8 项、每项不超过 240 字符、
整字段不超过 1,200 字符,且公开安全。Host 必须保留实际
`delivery_batch_scale` 与 `delivery_outcome`,不能改写成固定的 progress 分类。外层
Turn executor 验证 typed result 并保留证据,`turn run-once` 以 `host_evidence`
投影回读;旧 journal replay 可保留缺少这些字段的历史 material result,stop result
不得返回两组证据。Host 不取得写回或扣额权。历史 profile 保留原签名;两种契约都不
存在时保持原 prompt。

No new frontend/Lark operation or configuration is introduced. Regenerate saved
expanded heartbeat prompts to consume the new contract; bootstrap prompts reload
Expand Down
7 changes: 6 additions & 1 deletion loopx/control_plane/quota/turn_envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ const CONTRACT_CAPSULE_FIELDS: Readonly<Record<string, readonly string[]>> = {
"selected_next_due_at", "non_runnable_non_monitor_count",
"material_transition", "action",
],
execution_profile: ["cadence", "minimum_scale", "spend_rule", "must_include"],
execution_obligation: [
"kind", "contract", "contract_obligation", "must_attempt_work",
"notify_is_execution_gate", "delivery_allowed", "reason",
Expand Down Expand Up @@ -538,6 +537,12 @@ function contractCapsule(
}
const unavailableContext = projectTurnStartUnavailableContext(payload.turn_start_capability_hook_dispatch);
if (unavailableContext) capsule.unavailable_context = unavailableContext;
// Work qualification is behavioral context, including its outcome and
// degradation rules. Display-field filtering must not weaken that contract.
const executionProfile = object(payload.execution_profile);
if (Object.keys(executionProfile).length > 0) {
capsule.execution_profile = structuredClone(executionProfile);
}
// These existing source fields govern evidence and continuation, rather
// than summarize display text. Preserve every clause, order, empty value
// and current judge without copying the detailed acceptance-gap ledger.
Expand Down
35 changes: 31 additions & 4 deletions loopx/control_plane/testing/cli_output_differential.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,14 +842,41 @@ def migrated(clause: str) -> bool:
}, ["reviewed continuation instructions migrated; bounded one-time growth"]


def _inline_agent_context_migration(
def _turn_projection_migration(
base: dict[str, Any], candidate: dict[str, Any],
) -> tuple[dict[str, int], list[str]]:
old_paths = set(base.get("json_shape_paths") or [])
new_paths = set(candidate.get("json_shape_paths") or [])
profile = "$.turn_envelope.contract_capsule.execution_profile"
restored = {f"{profile}.{field}" for field in (
"small_scale_streak_threshold", "outcome_floor", "outcome_floor.configured",
"outcome_floor.surface_streak_threshold", "outcome_floor.must_advance",
"outcome_floor.must_advance[]",
)}
if (
str(base.get("row_id", "")).startswith("surface/loopx_turn_plan/")
and candidate.get("row_id") == base["row_id"]
and base.get("format") == candidate.get("format") == "json"
and {f"{profile}.{field}" for field in (
"cadence", "minimum_scale", "spend_rule", "must_include", "must_include[]",
)} <= old_paths
and old_paths.isdisjoint(restored)
and old_paths <= new_paths and new_paths - old_paths == restored
and base.get("action_signature_coverages") == candidate.get("action_signature_coverages")
and base.get("action_signature_coverages") in (
[ACTION_SIGNATURE_COVERAGE_V0], [ACTION_SIGNATURE_COVERAGE_V2],
)
):
# Same 96-row fixture: restoring the native outcome/degradation rules
# adds 138–142 JSON chars/bytes, zero lines. Keep 18 chars of headroom
# only for this first complete projection; later/partial/unrelated
# growth and absolute ceilings retain their existing guards.
return {metric: 160 for metric in ("chars", "utf8_bytes", "compact_payload_chars")}, [
"complete execution profile restored; bounded one-time JSON growth"
]
# The unchanged public multi-subagent fixture grows 1,147 JSON characters
# when its bounded contribution replaces a historical reference. Qualify
# only that transition (133 chars headroom), never later inline growth.
old_paths = set(base.get("json_shape_paths") or [])
new_paths = set(candidate.get("json_shape_paths") or [])
if (
base.get("row_id") == "variant/quota_should_run_turn_envelope_multi_subagent/small/json"
and candidate.get("row_id") == base["row_id"]
Expand Down Expand Up @@ -889,7 +916,7 @@ def _compare_row_growth(
review_signals.extend(authoring_signals)
continuation_allowance, continuation_signals = _continuation_instruction_migration(base, candidate)
review_signals.extend(continuation_signals)
context_allowance, context_signals = _inline_agent_context_migration(base, candidate)
context_allowance, context_signals = _turn_projection_migration(base, candidate)
review_signals.extend(context_signals)
deltas: dict[str, int | None] = {}
allowances: dict[str, int | None] = {}
Expand Down
36 changes: 34 additions & 2 deletions loopx/control_plane/turn_driver/codex_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from .executor import (
HOST_AGENT_VISION_JSON_MAX_CHARS,
HOST_REWARD_MEMORY_REFLECTION_JSON_MAX_CHARS,
HOST_RESULT_EVIDENCE_LIMITS,
HOST_RESULT_TEXT_LIMITS,
LOOPX_TURN_HOST_REQUEST_SCHEMA_VERSION,
reward_memory_automation_enabled,
Expand Down Expand Up @@ -253,6 +254,14 @@ def codex_cli_result_schema(
},
"summary": {"type": "string", "maxLength": text_limits["summary"]},
}
for evidence_field, (max_items, max_item_chars, _max_total_chars) in (
HOST_RESULT_EVIDENCE_LIMITS.items()
):
properties[evidence_field] = {
"type": "array",
"items": {"type": "string", "maxLength": max_item_chars},
"maxItems": max_items,
}
if reward_memory_automation_enabled(request, operation="automatic_ingest"):
properties["reward_memory_reflection_json"] = {
"type": "string",
Expand All @@ -266,12 +275,34 @@ def codex_cli_result_schema(
"enum": ["codex-cli"],
}
properties["child_execution_receipts"] = child_receipts
return {
envelope = _mapping(request.get("turn_envelope")) if request else {}
capsule = _mapping(envelope.get("contract_capsule"))
profile = capsule.get("execution_profile")
evidence_required = isinstance(profile, Mapping) and bool(profile)
schema: dict[str, Any] = {
"type": "object",
"properties": properties,
"required": list(properties),
"required": [
field for field in properties
if field not in HOST_RESULT_EVIDENCE_LIMITS
],
"additionalProperties": False,
}
if evidence_required:
schema["allOf"] = [
{
"if": {
"properties": {
"result_kind": {"enum": list(CODEX_CLI_RESULT_KINDS[:3])}
},
"required": ["result_kind"],
},
"then": {
"required": list(HOST_RESULT_EVIDENCE_LIMITS),
},
}
]
return schema


def _prompt(request: Mapping[str, Any]) -> str:
Expand All @@ -283,6 +314,7 @@ def _prompt(request: Mapping[str, Any]) -> str:
"Use the TurnEnvelope as the source of truth. Read work_context and remaining required_reads before primary_action; do not repeat fulfilled reads; perform work only when its contract allows it. The selected Todo summary is not its full requirements, and completing that work does not prove Goal completion.",
"Do not write LoopX state, spend quota, or apply scheduler changes; the adapter owns those effects.",
"Return only the schema-constrained result. For validated_progress, repair_required, or replan_required, fill every material field with public-safe evidence.",
"For those material results, preserve the actual delivery_batch_scale and delivery_outcome selected from the signed execution_profile's work and outcome requirements. Return work_evidence and validation_evidence as separate arrays of short public-safe strings; name the artifact or observed work in the first and the independent command or readback in the second. Omit both arrays for stop results.",
"For those material results, set path_delta_mode=material_replan only when this Turn changes a prior assumption, route, scope, acceptance rule, or stops prior work; then provide a complete bounded agent vision packet with goal_path_delta_v0 in agent_vision_json and leave vision_unchanged_reason empty.",
"For routine continuation, retry, successor creation, or no-change replanning, set path_delta_mode=unchanged, leave agent_vision_json empty, and provide vision_unchanged_reason.",
"For user_action_required, wait, or iteration_failed, leave material-only fields empty and explain the stop in summary. iteration_failed ends only this iteration and never requests a retry or successor.",
Expand Down
8 changes: 8 additions & 0 deletions loopx/control_plane/turn_driver/execution_readback.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ def execution_payload(
journal.get("completed_phases") or []
)
recovery = journal.get("recovery_audit")
host_result = _mapping(journal.get("host_result"))
host_evidence = {
field: list(host_result[field])
for field in ("work_evidence", "validation_evidence")
if isinstance(host_result.get(field), list)
and host_result[field]
}
return {
"ok": journal.get("status")
in {
Expand All @@ -59,6 +66,7 @@ def execution_payload(
"would_invoke_host": plan["route"]["would_invoke_host"],
}} if not execute else {}),
"result_kind": journal.get("result_kind"),
**({"host_evidence": host_evidence} if host_evidence else {}),
"validation": journal.get("task_validation"),
**({"validation_stage": journal["validation_stage"]} if journal.get("validation_stage") else {}),
"receipt": journal.get("receipt"),
Expand Down
Loading
Loading