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 @@ -769,6 +769,21 @@ promotion retain their own acceptance. No new paid cohort or soak is authorized.
qualify the remaining effect owners before existing-project activation or
global routing can open.

### 2026-09-27: first-party Host runtime partial enforcement

- **Baseline:** `fd96e5e2574272262b9ea604a96581a0d20e94d1`
- **Delivered:** A TypeScript-owned exact GoalRef decision and alias-scoped
lifecycle guard for source-profile Turn journals, Codex descriptors, DSH
session identity, and the Kunlun native runtime journal.
- **Evidence:** Negative tests cover Goal A results returning after same-alias
Goal B publication, cached Turn-result recovery, legacy Host state,
cross-instance session selection, and serialized result/recreation commits.
Non-source plans, paths, schemas, and persisted bytes retain legacy behavior.
- **Remaining hold:** This is partial M3 enforcement. Accepted-before-retirement
downstream drain, unsupported/warm binaries, and the remaining inventory
owners are not qualified. `execution_authority: false` and the M3 activation
hold remain unchanged.

## Appendix B: Decision log

| Date | Decision | Owner / approval | Alternatives | Normative sections changed |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,19 @@ service adoption、D1–D3 provider promotion 保留各自验收。不授权付
- **剩余 hold:** 所有结果均为 `execution_authority: false`。M3 必须先完成其余
effect owner 资格化,才能开放既有项目 activation 或 global routing。

### 2026-09-27:第一方 Host runtime 部分 enforcement

- **基线:** `fd96e5e2574272262b9ea604a96581a0d20e94d1`
- **已交付:** 为 source profile 的 Turn journal、Codex descriptor、DSH session
identity 和 Kunlun native runtime journal 增加 TypeScript-owned exact GoalRef
决策与 alias-scoped lifecycle guard。
- **证据:** 负向测试覆盖同名 Goal B 发布后 Goal A 结果迟到、缓存 Turn result
恢复、legacy Host state、跨实例 session selection,以及 result/recreation
commit 串行化。非 source plan、路径、schema 与持久化字节保持 legacy 行为。
- **剩余 hold:** 这只是 M3 的部分 enforcement。accepted-before-retirement 的
downstream drain、不支持的旧/常驻二进制和其余 inventory owner 尚未
qualified;`execution_authority: false` 与 M3 activation hold 保持不变。

## 附录 B:决策日志

| 日期 | 决策 | Owner/批准 | 替代方案 | 变更的规范章节 |
Expand Down
75 changes: 62 additions & 13 deletions loopx/cli_commands/turn.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
from ..capabilities.periodic_report.cadence_runtime import extend_cadence_turn_start_dispatch
from ..control_plane.quota.live_decision import build_live_quota_should_run_decision
from ..control_plane.agents.workspace_guard import capture_delivery_workspace
from ..control_plane.goals.first_party_host_admission import (
FirstPartyHostGoalAdmission,
capture_first_party_host_goal_ref,
)
from ..control_plane.quota.heartbeat_receipt import ensure_turn_heartbeat_settlement_receipt
from ..control_plane.quota.settlement import (
SettlementIdentity,
Expand Down Expand Up @@ -116,6 +120,18 @@ def handle_turn_command(
registry_path=registry_path,
runtime_root_override=runtime_root_arg,
)
goal_ref = capture_first_party_host_goal_ref(
registry_path=registry_path,
goal_id=args.goal_id,
)
goal_admission = FirstPartyHostGoalAdmission.for_plan(
registry_path=registry_path,
goal_id=args.goal_id,
planned_goal_ref=goal_ref,
)
strict_goal_admission = (
goal_admission if goal_admission.enabled else None
)
# Planning and dry-run execution inspect existing admitted intents.
# Only an executing wake may sync inboxes or reserve a calendar window.
turn_start_hook_dispatch = {}
Expand Down Expand Up @@ -167,7 +183,15 @@ def handle_turn_command(
and not args.resume_turn_key
and turn_envelope.get("effective_action") != EffectiveAction.GOVERNED_CAPABILITY_INTENT.value
):
session_binding = codex_cli_session_binding(runtime_root, turn_envelope)
session_binding = (
codex_cli_session_binding(
runtime_root,
turn_envelope,
goal_admission=strict_goal_admission,
)
if strict_goal_admission is not None
else codex_cli_session_binding(runtime_root, turn_envelope)
)
payload = build_loopx_turn_plan(
turn_envelope,
host=args.host,
Expand All @@ -176,6 +200,7 @@ def handle_turn_command(
session_binding=session_binding,
turn_instance_id=args.turn_instance_id,
iteration_context_policy=args.iteration_context.replace("-", "_"),
goal_ref=goal_ref,
)
# The executor readback names where this Turn's model work runs and
# whether that host can launch here, so a caller never has to infer it
Expand Down Expand Up @@ -296,6 +321,16 @@ def handle_turn_command(
raise ValueError(
"LoopX Turn resume journal belongs to another agent"
)
goal_admission = FirstPartyHostGoalAdmission.for_plan(
registry_path=registry_path,
goal_id=args.goal_id,
planned_goal_ref=payload.get("goal_ref"),
)
strict_goal_admission = (
goal_admission if goal_admission.enabled else None
)
if strict_goal_admission is not None:
strict_goal_admission.require_current()
if payload.get("route", {}).get("kind") == "capability_action_required":
# The normal host transaction forbids Core mutations. A
# capability may prepare artifacts and require authored input;
Expand Down Expand Up @@ -986,24 +1021,37 @@ def scheduler(_spend_payload: dict[str, object]) -> dict[str, object]:
def run_built_in_host(
request: Mapping[str, Any],
) -> dict[str, Any]:
return run_codex_cli_host(
request,
runtime_root=runtime_root,
project=project,
codex_bin=args.codex_bin,
sandbox=args.codex_sandbox,
model=args.codex_model,
reasoning_effort=args.codex_reasoning_effort,
mcp_server=args.codex_mcp_server_json,
timeout_seconds=max(1.0, args.timeout_seconds - 5.0),
)
options = {
"runtime_root": runtime_root,
"project": project,
"codex_bin": args.codex_bin,
"sandbox": args.codex_sandbox,
"model": args.codex_model,
"reasoning_effort": args.codex_reasoning_effort,
"mcp_server": args.codex_mcp_server_json,
"timeout_seconds": max(1.0, args.timeout_seconds - 5.0),
}
if strict_goal_admission is not None:
options["goal_admission"] = strict_goal_admission
return run_codex_cli_host(request, **options)

host_runner = run_built_in_host

def resolve_built_in_session_binding(
turn_envelope: Mapping[str, Any],
) -> dict[str, str] | None:
return codex_cli_session_binding(runtime_root, turn_envelope)
return (
codex_cli_session_binding(
runtime_root,
turn_envelope,
goal_admission=strict_goal_admission,
)
if strict_goal_admission is not None
else codex_cli_session_binding(
runtime_root,
turn_envelope,
)
)

session_binding_resolver = resolve_built_in_session_binding
elif args.host == "dsh":
Expand Down Expand Up @@ -1079,6 +1127,7 @@ def on_managed_start_admitted() -> None:
),
admit_start=managed_cadence.admit if args.execute else None,
confirm_start=managed_cadence.confirm if args.execute else None,
goal_admission=strict_goal_admission,
)
else:
raise ValueError("turn requires the `plan` or `run-once` subcommand")
Expand Down
2 changes: 2 additions & 0 deletions loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ import {
decideProjectSessionBind,
decideProjectSessionUnbind,
} from "./goals/source_session_lifetime.ts";
import { decideFirstPartyHostRuntime } from "./goals/first_party_host_runtime.ts";
import {
evaluateDeliveryRoute,
} from "./turn_driver/delivery_continuity.ts";
Expand Down Expand Up @@ -536,6 +537,7 @@ export function createEffectRuntimeHandlers(
["goal.source_session.bind.decide", decideProjectSessionBind],
["goal.source_session.unbind.decide", decideProjectSessionUnbind],
["goal.source_session.recreate.decide", decideGoalRecreation],
["goal.first_party_host_runtime.decide", decideFirstPartyHostRuntime],
["goal.acceptance.inspect", inspectLocalGoalAcceptance],
["goal.acceptance.configure", commitLocalGoalAcceptance],
["goal.acceptance.verify.commit", commitLocalGoalAcceptanceVerification],
Expand Down
Loading
Loading