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
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ appendix may keep dated history, but no dated log heading may precede it.
| [RFC: Live Team Workspace v0](live-team-workspace-v0.md) | Accepted | none | — |
| [RFC: Long-Horizon Harness Benchmark and Research Program v0](long-horizon-harness-benchmark-research-program-v0.md) | Accepted | none | — |
| [RFC: Long-Running Agent Reliability Diagnostics and Governed Delivery v0](long-running-agent-reliability-diagnostics-governed-delivery-v0.md) | Accepted | none | — |
| [LoopX Overall Roadmap v0: Product, Collaboration, Technology and Delivery](loopx-overall-roadmap-v0.md) | Accepted | none | — |
| [LoopX Overall Roadmap v0: Product, Collaboration, Technology and Delivery](loopx-overall-roadmap-v0.md) | Accepted | none | [1 entry](ledger/loopx-overall-roadmap-v0/) |
| [Manager runtime profile v0](manager-runtime-profile-v0.md) | Accepted | none | — |
| [RFC: Monorepo Distribution Split (v0)](monorepo-distribution-split-v0.md) | Accepted | none | — |
| [RFC: Obelisk Session Evidence Provider v0](obelisk-session-evidence-provider-v0.md) | Accepted | none | — |
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/STATUS.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
| [RFC:团队实时工作区 v0](live-team-workspace-v0.zh-CN.md) | 已接受 | 无 | — |
| [RFC:长程 Harness Benchmark 与研究计划 v0](long-horizon-harness-benchmark-research-program-v0.zh-CN.md) | 已接受 | 无 | — |
| [RFC:长程 Agent 可靠性诊断与治理交付 v0](long-running-agent-reliability-diagnostics-governed-delivery-v0.zh-CN.md) | 已接受 | 无 | — |
| [LoopX 整体路线总纲 v0:产品、协作、技术与交付](loopx-overall-roadmap-v0.zh-CN.md) | 已接受 | 无 | — |
| [LoopX 整体路线总纲 v0:产品、协作、技术与交付](loopx-overall-roadmap-v0.zh-CN.md) | 已接受 | 无 | [1 条](ledger/loopx-overall-roadmap-v0/) |
| [Manager runtime profile v0 / 管家运行模式 v0](manager-runtime-profile-v0.zh-CN.md) | 已接受 | 无 | — |
| [RFC:Monorepo 内的发行物拆分(v0)](monorepo-distribution-split-v0.zh-CN.md) | 已接受 | 无 | — |
| [RFC:Obelisk Session Evidence Provider v0](obelisk-session-evidence-provider-v0.zh-CN.md) | 已接受 | 无 | — |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# S2/S12 startup diagnostic checkpoint (2026-10-04)

Recorded for [#5551](https://github.com/loopx-project/loopx/pull/5551); the
[roadmap](../../loopx-overall-roadmap-v0.md) body keeps a one-line pointer here.

When the managed Effect runtime cannot publish its startup locator, the failure
now travels through the existing typed startup envelope and reuses the shared
filesystem/lock codes (`io_is_directory`, `mutation_lock_timeout`, Windows
`io_permission_denied` for an occupied locator directory) instead of surfacing
only as `runtime_exited_before_ready` from an unhandled listen-callback
rejection. The bounded message carries no raw Node error, locator path, token or
stack trace.

Real fixtures occupy the locator with a directory and hold a live mutation lock.
Both fail exactly once with the exact code, leave the foreign occupant and its
lock untouched, remove the runtime's own start lock, and resume normal
ping/shutdown once the injected fault is gone. An exit without a typed envelope
still reports `runtime_exited_before_ready`.

This closes the reproduced missing diagnostic only. It does not attribute the
unrelated Linux unexpected-exit CI failure, qualify Linux/Windows CI as passing,
or claim installation, release or strict publication readiness. The
operator-facing boundary is in the
[installation guide](../../../../guides/installing-loopx.md).
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# S2/S12 启动诊断检查点(2026-10-04)

本条目为 [#5551](https://github.com/loopx-project/loopx/pull/5551) 记录;
[总纲](../../loopx-overall-roadmap-v0.zh-CN.md) 正文只保留指向本条目的一行指针。

managed Effect runtime 无法发布启动 locator 时,失败现在走既有 typed 启动
envelope,并复用共享的文件系统/锁诊断码(`io_is_directory`、
`mutation_lock_timeout`,Windows 占用 locator 目录时为 `io_permission_denied`),
不再只表现为 listen callback 未处理拒绝导致的 `runtime_exited_before_ready`。
该固定消息不包含原始 Node 错误、locator 路径、token 或堆栈。

真实 fixture 分别用目录占用 locator、以及活进程持有 mutation lock 两种故障:
两者都只失败一次并返回准确 code,不修改外来占用与其锁,清理 runtime 自己的
start lock,并在移除注入故障后恢复正常的 ping/shutdown。没有 typed envelope 的
退出仍然报告 `runtime_exited_before_ready`。

本次只关闭已复现的“缺失诊断”缺口:既不为无关的 Linux unexpected-exit CI 失败
归因,也不宣称 Linux/Windows CI 通过,更不代表安装、发布或严格 publication
readiness。面向操作者的边界见
[安装指南](../../../../guides/installing-loopx.md)(中文说明在该文件内)。
11 changes: 11 additions & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ creation path; see the [absorption map](app-conversation-and-async-inbox-v0.md#c
Its separate workspace/executor is not adopted. This entry improvement does not
close GQ01 execution/return or R2 small-team acceptance.

**S2/S12 startup diagnostic checkpoint (2026-10-04)** moved to
[`ledger/loopx-overall-roadmap-v0/2026-10-04-s2-s12-startup-diagnostic.md`](ledger/loopx-overall-roadmap-v0/2026-10-04-s2-s12-startup-diagnostic.md).

## 1. Overall Objective and Product Routes

LoopX aims to let people express, revise and accept complex goals through a local frontend or Lark, while a persistent steward coordinates long-running LoopX Agents with independent work commitments across local managed and cloud runtimes. Single-Agent long-horizon reliability is the foundation. Multi-Agent collaboration, handoff, recovery and convergence on shared goals are core capabilities. Hundred-Agent scale is a separate system qualification.
Expand Down Expand Up @@ -932,3 +935,11 @@ uv run --extra test python -m pytest -q tests/test_turn_managed_executor_binding
These 177 tests are not a full repository run or live cloud/model, packaged-browser, Lark or PostgreSQL qualification. The #4552 browser fixture and live source-read record in the selection RFC are historical evidence, not rerun here, and do not qualify team execution. F1–F4 reproduction steps are fixed in the findings table; implementation should add the corresponding independent semantic regressions to existing tests, not commit temporary diagnostic scripts or private run logs.

Update the current assessment, card boundaries and qualifying evidence in place. Move long historical ledgers to companions and keep domain RFC status synchronized. If domain state/authority/migration contracts conflict, stop affected implementation and repair the documents rather than overriding accepted authority through this roadmap. Merging this document accepts a discoverable, claimable design route; it does not complete R1–R7 or pass implementation and promotion gates.

## Appendix A: Execution ledger

Dated checkpoints for this RFC are files under
[`ledger/loopx-overall-roadmap-v0/`](ledger/loopx-overall-roadmap-v0/), one dated
entry per change, named and paired per [the ledger convention](ledger/README.md).
An entry states what the change measured, what it changed, and what it did not
establish; this body keeps only a pointer to it.
8 changes: 8 additions & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

**本地权威收尾检查点(2026-10-02)。** R5/T4 使用按 `9b0486dc1` 复核的[验证→迁移→删除计划](ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md#当前收尾验证迁移与删除2026-10-02)。先收尾现有 #5413/#5466/#5283,验证一个安装态可回退候选,再分别决定有界自愿试用和发布默认准入。Canonical 创建、legacy 策略迁移与最后 writer 删除各有明确出口;Python 替代 owner 随最后调用方删除。R6 独立,不用固定剩余 PR 数或历史测试数量证明完成。

**S2/S12 启动诊断检查点(2026-10-04)** 已移至[执行账本](ledger/loopx-overall-roadmap-v0/2026-10-04-s2-s12-startup-diagnostic.zh-CN.md)。

## 1. 总目标与产品路线

LoopX 的目标是让人用本地前端或 Lark 提出、修订和验收复杂目标,由持久管家协调多个拥有独立工作承诺的长程 LoopX Agent,在本地 managed 与云端 runtime 上持续完成可验证的工作。单 Agent 的长程可靠性是基础,多个 Agent 的协作、handoff、恢复和共享目标收敛是核心能力,百 Agent 规模是需要独立证明的系统资格。
Expand Down Expand Up @@ -710,3 +712,9 @@ uv run --extra test python -m pytest -q tests/test_turn_managed_executor_binding
这 177 项不是全仓测试,也不是云端、真实模型、packaged browser、Lark 或 PostgreSQL 现场资格。#4552 的 browser fixture 与选型 RFC 记录的既有现场读取为历史证据,本轮未复跑,不能推广到团队执行验收。F1–F4 的复现步骤在上表固定,实施时将对应的独立语义反例加入已有测试,不提交本次临时诊断脚本或私有运行日志。

维护规则:本页只更新当前判断、卡的边界及通过证据;历史长账本移至 companion,领域 RFC 的状态与这里同步。领域状态/权限/迁移规则发生冲突时,停相关实现并修正文档,不用本路线覆盖已接受的 authority 合同。该文档合并表示路线可发现,不表示 R1–R7 已完成或 实现或晋升门槛已通过。

## 附录 A:执行账本

本 RFC 的带日期检查点是 [`ledger/loopx-overall-roadmap-v0/`](ledger/loopx-overall-roadmap-v0/) 下的文件,
一次改动一条带日期的条目,命名与镜像配对遵循[账本约定](ledger/README.zh-CN.md)。条目写清这次改动测到了
什么、改了什么、以及没有确立什么;本页正文只保留指向它的一行指针。
14 changes: 14 additions & 0 deletions docs/guides/installing-loopx.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ runtime with `loopx doctor --restart-runtime` after the new Node is on `PATH`.
SQLite remains opt-in and checks the actual embedded SQLite version before
opening authority state.

A managed runtime that cannot publish its startup locator returns the existing
safe filesystem/lock diagnostic code, such as `mutation_lock_timeout`,
`io_is_directory`, or Windows `io_permission_denied` for an occupied locator
directory, instead of only an exit status. The message does not echo
locator paths, tokens or Node stack traces. Inspect the named local ownership
or filesystem problem before retrying; the diagnostic neither removes a live
owner's lock nor repairs a foreign locator. It does not identify every possible
startup crash: an exit without a typed envelope remains `runtime_exited_before_ready`.

中文:启动 locator 发布失败会返回既有文件系统/锁诊断码,不回显路径、token 或
Node 堆栈。先核对本机对应的占用或文件系统问题;诊断不会删除活进程持有的锁、
修复未知 locator,也不证明所有启动退出都已归因。没有 typed envelope 的退出仍
保留 `runtime_exited_before_ready`。

The CI and release lanes use Node.js 24 LTS. Node.js 26 remains a non-blocking
forward-compatibility probe and is not a supported-version promise. After the
Node.js 22 maintenance window ends, a separate policy change will raise the
Expand Down
40 changes: 24 additions & 16 deletions loopx/control_plane/effect_runtime_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,21 +283,29 @@ server.on("close", () => {
});

server.listen(0, "127.0.0.1", async () => {
const address = server.address();
if (!address || typeof address === "string") throw new Error("invalid address");
await withFileMutationLock(infoPath, async () => {
await atomicWriteJson(infoPath, {
schema_version: INFO_SCHEMA,
fingerprint,
pid: process.pid,
host: "127.0.0.1",
port: address.port,
token,
// A managed runtime is reused per source revision, so the Node/SQLite pair
// serving a goal is not necessarily the one the caller resolves from PATH.
runtime_identity: sqliteRuntimeIdentity(),
try {
const address = server.address();
if (!address || typeof address === "string") throw new Error("invalid address");
await withFileMutationLock(infoPath, async () => {
await atomicWriteJson(infoPath, {
schema_version: INFO_SCHEMA,
fingerprint,
pid: process.pid,
host: "127.0.0.1",
port: address.port,
token,
// A managed runtime is reused per source revision, so the Node/SQLite pair
// serving a goal is not necessarily the one the caller resolves from PATH.
runtime_identity: sqliteRuntimeIdentity(),
});
await chmod(infoPath, 0o600);
});
await chmod(infoPath, 0o600);
});
resetIdleTimer(server);
resetIdleTimer(server);
} catch (error) {
// Reuse the shared error owner's safe codes, never the raw Node error,
// locator path, token or stack. The launcher already consumes this startup
// envelope; publication failure must not collapse into a bare exit code.
const { code } = effectRuntimeErrorPayload(error);
failStartup(code, `TypeScript Effect runtime could not publish its startup locator (${code})`);
}
});
59 changes: 59 additions & 0 deletions tests/control_plane/test_effect_runtime_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,65 @@ def terminate(self) -> None:
assert clock["monotonic"] == 1.5


@pytest.mark.parametrize(
("failure", "expected_code"),
[
# Windows rejects replacement of an occupied directory with EPERM/EACCES;
# preserve that platform's shared permission diagnostic rather than
# requiring the Unix EISDIR classification.
("directory", "io_permission_denied" if os.name == "nt" else "io_is_directory"),
("live_lock", "mutation_lock_timeout"),
],
)
def test_locator_publication_failure_surfaces_safe_typed_startup_diagnostic(
tmp_path: Path,
monkeypatch,
failure: str,
expected_code: str,
) -> None:
marker = "private-locator-fixture"
runtime_dir = tmp_path / marker
runtime_dir.mkdir(mode=0o700)
monkeypatch.setattr(effect_runtime, "_runtime_dir", lambda: runtime_dir)
info_path = effect_runtime._runtime_info_path(effect_runtime._runtime_fingerprint())
lock_path = Path(f"{info_path}.ts-effect.lock")
lock_owner = {"pid": os.getpid(), "token": "private-lock-token-fixture"}
if failure == "directory":
info_path.mkdir()
else:
lock_path.write_text(json.dumps(lock_owner), encoding="utf-8")

captures: list[bytes] = []
read_stderr = effect_runtime._read_startup_stderr

def capture(stream):
raw = read_stderr(stream)
captures.append(raw)
return raw

monkeypatch.setattr(effect_runtime, "_read_startup_stderr", capture)
with pytest.raises(effect_runtime.EffectRuntimeStartupError) as raised:
effect_runtime.effect_runtime_result("runtime.ping", {}, retry_safe=False)

assert raised.value.diagnostic_code == expected_code
assert "could not publish its startup locator" in str(raised.value)
assert len(captures) == 1
envelope = json.loads(captures[0])
assert envelope == {
"schema_version": effect_runtime.EFFECT_RUNTIME_STARTUP_ERROR_SCHEMA_VERSION,
"code": expected_code,
"message": str(raised.value),
}
assert marker not in captures[0].decode()
assert lock_owner["token"] not in captures[0].decode()
assert not list(runtime_dir.glob("start-*.lock"))
if failure == "directory":
assert info_path.is_dir(), "startup diagnostics must not repair a foreign locator"
else:
assert not info_path.exists(), "a failed publisher must not claim readiness"
assert json.loads(lock_path.read_text()) == lock_owner


def test_early_runtime_exit_surfaces_stable_startup_diagnostic(
tmp_path: Path,
monkeypatch,
Expand Down
Loading