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
42 changes: 41 additions & 1 deletion docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,18 @@ records both edges. Existing Ark/JEV private edges remain migration debt and
receive no new exemption. The facade forwards existing behavior; it must not
copy candidate, acceptance, process-lifetime or locking decisions.

One more public module joins that seam. `loopx.runtime_activation` owns the
transition of services that are already installed, and its
`managed_chat_activation_guard` holds the accepted-Chat-work fence and the
ingress lock while a managed client restarts. `loopx-codex-provider-routing`
uses that guard, and no other entry point of that module, to fence accepted Chat
work before it restarts the Chat services it manages. The guard grants no work
authority: it holds the ingress admission lock and refuses to continue while a
non-terminal Turn is pending. A pending ingress receipt is not part of that
check. The import ratchet records that edge as well. This
is a third public module in the seam and does not widen it: a package still may
not import the executor or another kernel-private module.

The stdio relay cancels its owned process group on parent EOF, SIGTERM or
SIGINT and waits for the existing supervisor's cleanup before exiting. POSIX
process-group behavior and Windows best-effort tree cleanup retain their
Expand Down Expand Up @@ -696,11 +708,39 @@ Approval needs a reviewed decision, not implementation progress or silence.
- **Remaining:** the wider M1–M5 distribution and zero-private-import acceptance
is not closed; D1–D4 stay open. No packaging or authority decision changes.

### 2026-10-29 — Codex provider-routing activation seam

- **Baseline:** `5d7f0a61b`.
- **Delivered:** the supported host integration seam in Section 5 names one more
public module. `loopx.runtime_activation` owns the transition of services that
are already installed, and `loopx-codex-provider-routing` uses its
`managed_chat_activation_guard` to hold the accepted-Chat-work fence and the
ingress lock while it restarts the Chat services it manages. The import
ratchet records that edge.
- **Boundary repair:** the guard grants no work authority — it holds the ingress
admission lock and refuses to continue while a non-terminal Turn is pending,
and a pending ingress receipt is not part of that check — and
the package uses no other entry point of that module. The seam stays closed: a
package still may not import the executor or another kernel-private module,
and the pinned Ark/JEV edges remain migration debt.
- **Evidence:** the import ratchet pins the edge instead of leaving it
unrecorded, and the Codex provider-routing native host smoke exercises the
guarded restart path. `tests/test_chat_runtime_activation.py::
test_a_pending_ingress_receipt_alone_does_not_defer_the_restart` pins the
narrowed boundary: a pending `loopx_queue` or `loopx_inbox` receipt on its own
does not defer the restart, because the guard reads accepted work through the
Turn state.
- **Remaining:** the wider M1–M5 distribution and zero-private-import acceptance
is not closed; D1–D4 stay open. The seam records one module for one caller,
not a general package-to-kernel permission.

## Appendix B: Decision log

| Date | Decision | Owner / approval | Alternatives | Normative sections changed |
| --- | --- | --- | --- | --- |
| — | — | — | — | — |
| 2026-10-29 | `loopx.runtime_activation` joins the supported host integration seam, so the Codex provider-routing package can hold the accepted-Chat-work fence while it restarts the Chat services it manages | Maintainers, through RFC review and merge | Restart the Chat services without the fence; copy the lock and state check into the package; keep the edge unrecorded | Section 5 |

No other decision is approved. D1–D4 remain open proposals.

## Appendix C: Evidence registry

Expand Down
33 changes: 32 additions & 1 deletion docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,15 @@ MemT 只使用这两个公开模块,不能直接 import executor 或其他内
边。Ark/JEV 的既有私有边仍是迁移积压,不新增豁免。facade 只转发现有行为,
不能复制候选、验收、进程生命周期或文件锁决策。

该接缝再增加一个公开模块。`loopx.runtime_activation` 负责已安装服务的切换,
其 `managed_chat_activation_guard` 在受管客户端重启期间持有"已受理 Chat 工作"
栅栏与入口锁。`loopx-codex-provider-routing` 只使用该栅栏、不使用该模块的其他
入口,在重启自己管理的 Chat 服务之前为已受理的 Chat 工作加栅栏。该栅栏不授予
工作权限:它持有入口准入锁,并在仍有非终态 Turn 时拒绝继续。待处理的入口收据
不在该检查范围内。import ratchet
同样钉住这条边。这是接缝中的第三个公开模块,并没有放宽接缝:包依旧不能 import
executor 或其他内核私有模块。

stdio relay 在父进程 EOF、SIGTERM 或 SIGINT 时取消所持进程组,并等待已有
监督器完成回收再退出。POSIX 进程组行为与 Windows 尽力而为的进程树回收仍沿用
原资格边界。这是进程监督,不是沙箱;操作者选择的工具只保有原先获准的效果。
Expand Down Expand Up @@ -498,11 +507,33 @@ R0 可以先于目录移动交付,但不代表内核合格。R3/R4 可选,
- **剩余:** 不关闭更广的 M1–M5 发行物与零私有 import 验收;D1–D4 仍未决。
不改变打包方案或权威决策。

### 2026-10-29 — Codex provider-routing 激活接缝

- **基线:** `5d7f0a61b`。
- **交付:** 第 5 节的受支持宿主接入接缝再增加一个公开模块。
`loopx.runtime_activation` 负责已安装服务的切换,
`loopx-codex-provider-routing` 使用其 `managed_chat_activation_guard`,在重启
自己管理的 Chat 服务期间持有"已受理 Chat 工作"栅栏与入口锁。import ratchet
钉住这条边。
- **边界修复:** 该栅栏不授予工作权限——它持有入口准入锁,并在仍有非终态 Turn 时
拒绝继续;待处理的入口收据不在该检查范围内——且该包不使用该模块的其他入口。
接缝仍然闭合:包依旧不能
import executor 或其他内核私有模块,已钉住的 Ark/JEV 私有边仍是迁移积压。
- **证据:** import ratchet 钉住这条边,而不是让它处于未记录状态;Codex
provider-routing 的 native host smoke 覆盖受保护的重启路径。
`tests/test_chat_runtime_activation.py::test_a_pending_ingress_receipt_alone_does_not_defer_the_restart`
钉住收窄后的边界:只有一条待处理的 `loopx_queue` 或 `loopx_inbox` 收据并不会
推迟重启,因为该栅栏是通过 Turn 状态读取已受理工作的。
- **剩余:** 不关闭更广的 M1–M5 发行物与零私有 import 验收;D1–D4 仍未决。
该接缝只为一个调用方记录一个模块,不是通用的包到内核许可。

## 附录 B:决策日志

| 日期 | 决策 | 负责人 / 批准 | 备选 | 变更的规范章节 |
| --- | --- | --- | --- | --- |
| — | — | — | — | — |
| 2026-10-29 | `loopx.runtime_activation` 加入受支持的宿主接入接缝,使 Codex provider-routing 包能在重启自己管理的 Chat 服务期间持有"已受理 Chat 工作"栅栏 | maintainers,经 RFC 评审与合并 | 不带栅栏重启 Chat 服务;把锁与状态检查复制进包;让这条边保持未记录 | 第 5 节 |

除此以外没有已批准的决策。D1–D4 仍是未决提案。

## 附录 C:证据登记

Expand Down
7 changes: 5 additions & 2 deletions tests/architecture/package_kernel_import_pins.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"schema": "loopx_package_kernel_import_pins_v0",
"baseline_commit": "70bc98198ce9e8c33e461cc41fe77243d713dcf0",
"note": "Exact pin of every `loopx.*` module imported by a co-located package. Pre-existing private edges remain migration debt. The supported-host-integration decision in the monorepo-distribution-split RFC Section 5 permits MemT's two public integration imports; it does not permit private kernel imports. Remove an entry in the PR that removes the import. Adding an entry needs a recorded design decision.",
"baseline_commit": "5d7f0a61b0b79876801c05e1d26352687f270794",
"note": "Exact pin of every `loopx.*` module imported by a co-located package. Pre-existing private edges remain migration debt. The supported-host-integration decision in the monorepo-distribution-split RFC Section 5 permits MemT's two public integration imports and the Codex provider-routing activation guard; it does not permit private kernel imports. Remove an entry in the PR that removes the import. Adding an entry needs a recorded design decision.",
"imports": {
"loopx-ark-turn": [
"loopx.control_plane.turn_driver.host_candidate",
"loopx.file_lock"
],
"loopx-codex-provider-routing": [
"loopx.runtime_activation"
],
"loopx-finance-value-discovery": [
"loopx.extensions.lark.presentation.message_card",
"loopx.extensions.presentation"
Expand Down
7 changes: 4 additions & 3 deletions tests/architecture/test_package_kernel_import_ratchet.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
contracts and CLI subcommands through `CapabilityRegistry`; it never imports
kernel-private modules." Three packages already do. The Section 5 supported
host integration decision adds MemT's public facade and shared file-lock API,
not another private-kernel exception. This guard pins those
edges exactly so a new one needs a recorded decision and a removed one
lowers the pin in the same PR, following `test_top_level_module_budget`.
plus the Codex provider-routing activation guard, not another private-kernel
exception. This guard pins those edges exactly so a new one needs a recorded
decision and a removed one lowers the pin in the same PR, following
`test_top_level_module_budget`.
"""

from __future__ import annotations
Expand Down
30 changes: 29 additions & 1 deletion tests/test_chat_runtime_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import pytest

from loopx.chat_store import ChatSessionStore
from loopx.chat_ingress import chat_runtime_activation_lock
from loopx.chat_ingress import chat_runtime_activation_lock, chat_runtime_activation_state
from loopx.runtime_activation import restart_services_for_runtime_activation
from loopx.self_update import execute_update_plan, render_update_plan_markdown

Expand Down Expand Up @@ -112,6 +112,34 @@ def test_queue_without_an_active_pointer_still_defers_until_original_work_finish
assert store.load_turn(sid, queued["turn_id"])["message"] == "later input"


@pytest.mark.parametrize("mode", ["loopx_queue", "loopx_inbox"])
def test_a_pending_ingress_receipt_alone_does_not_defer_the_restart(services, mode):
"""The guard fences non-terminal Turns, not pending ingress receipts.

A pending ingress receipt reserves external input before the provider
delivers it, so it is not a Turn yet. The activation guard reads the
accepted work through the Turn state, so a pending receipt on its own
does not defer the restart. This is the boundary the guard actually
holds, not a promise that pending receipts are covered.
"""
runtime, _agents, calls = services
store = ChatSessionStore(runtime)
sid = session(store)
receipt, created = store.create_ingress_receipt(
sid,
client_ingress_id=f"client-{mode}",
mode=mode,
message="reserved input",
)
assert created is True
assert receipt["status"] == "pending"
assert chat_runtime_activation_state(runtime)["nonterminal_turns"] == 0

result = activate()
assert result["restart_status"] == "restarted"
assert len(calls) == 2


@pytest.mark.parametrize(
"damage", ["session", "turn", "active_missing", "unknown_status", "invalid_status"]
)
Expand Down
Loading