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
27 changes: 27 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -3737,3 +3737,30 @@ model-token, service-cost and human-attention accounting. The Host remains
synthetic. Full V1–V5/V10, real model adoption/recovery, progressive product
control, R1/M3, independent Standard packages and original-goal regression remain
open. Preserve all nine original requirements and earlier frozen failures.

### 2026-11-13 — TraeX continuation consumes its runtime thread

The public `start-goal` entry now reads TraeX's `TRAECLI_THREAD_ID` through the
existing host-thread adapter. A verified `traex-cli` binding reuses its Agent
without another `--thread-id` or `--agent-id`; an explicit thread still takes
precedence. Missing, new, foreign-host and conflicting contexts retain the
existing identity fences. Trae App keeps its separate binding scope.

Matched isolated File/SQLite CLI checks cover source, the standalone core built
from `09886ba7a`, and the candidate core: 36 calls retain exact objective text,
registry/state bytes and canonical provider revision across ordinary entry,
explicit control/override, unknown context and replay. The base still loses the
ambient thread; both repaired entrypoints reuse the bound Agent. Source
regression passes 215 cases, including foreign-host and conflict refusal;
the installed core/recovery/context suite passes 23 on Python 3.11.15, with
source on Python 3.13.5 and Node 26.0.0. All 1,887 packaged runtime files match
the reviewed source bytes. Initial fixture setup failures remain failed attempts.

TraeX activation guidance reads native Goal tools or current host context first,
preserves an active original objective on continuation, and retains user stops.
Only an authorized fresh start with no existing native Goal may create one;
the visible `/goal` fallback remains for unavailable tools/context. This is an
entry and instruction repair, not proof that a model adopted the guidance.
No new lifecycle owner, model execution, preset or installed-host release is
introduced. Full R1/V1–V5/V10, progressive control, complete cost evidence and
the original-goal regression remain open; all original failures are retained.
22 changes: 22 additions & 0 deletions docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2915,3 +2915,25 @@ Node module/loopback/exit 事件,但不能提供 OS 级完整闭包、完整 I
token、service cost 或人工注意力核算。Host 仍为 synthetic。完整 V1–V5/V10、
真实模型采用/恢复、渐进产品控制、R1/M3、独立 Standard packages 与原目标回归
仍开放,保留原九项要求和此前冻结失败。

### 2026-11-13 — TraeX 续接读取运行时线程

公开 `start-goal` 入口现在通过既有 host-thread adapter 读取 TraeX 的
`TRAECLI_THREAD_ID`。已验证的 `traex-cli` 绑定自动复用原 Agent,无需再次
传入 `--thread-id` 或 `--agent-id`;显式线程仍优先。缺失、新线程、跨 Host 与
冲突上下文保留既有身份边界,Trae App 的绑定作用域仍独立。

隔离真实 File/SQLite CLI 对照覆盖源码、从 `09886ba7a` 构建的独立 core 与
候选 core:36 次调用在普通入口、显式对照/覆盖、未知上下文及重放中保留
目标原文、registry/state 字节与 canonical provider revision。基线仍丢失
ambient thread,两个修复后入口均复用已绑定 Agent。源码回归通过 215 项,
包含跨 Host 与冲突拒绝;已安装 core/恢复/context suite 在 Python 3.11.15
通过 23 项,源码使用 Python 3.13.5,Node 为 26.0.0。1,887 个打包 runtime
文件均与评审源码字节一致。最初夹具设置失败继续保留为失败尝试。

TraeX 激活指引先读取原生 Goal 工具或当前 Host 上下文,在续接时保留已有
active Goal 的原始目标和用户停止状态。仅当获授权的新启动确认不存在原生
Goal 时才可创建;工具/上下文不可用时仍保留可见 `/goal` 手工恢复路径。
这取得入口与指令修复证据,不能证明模型采用了指引。未新增生命周期 owner、
模型执行、preset 或已安装 Host 发布。完整 R1/V1–V5/V10、渐进控制、完整成本
证据与原目标回归仍开放,保留全部原始失败。
1 change: 1 addition & 0 deletions loopx/cli_commands/_host_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"codex-ide-plugin": "CODEX_THREAD_ID",
"codex-cli-tui": "CODEX_THREAD_ID",
"trae_app": "TRAECLI_THREAD_ID",
"traex-cli": "TRAECLI_THREAD_ID",
KIRO_CLI_AGENT_TYPE: KIRO_CLI_SESSION_ID_ENV,
}

Expand Down
11 changes: 7 additions & 4 deletions loopx/host_loop_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,23 +1116,26 @@ def _traex_activation(commands: dict[str, str]) -> dict[str, Any]:
"host_mutation": {
"owner": "TraeX CLI TUI",
"host_command": "/goal <task_body>",
"preferred_tool": "create_goal",
"cli_can_mutate_directly": False,
"requires_host_feature_flag": "[features] goals = true in ~/.trae/traecli.toml",
"missing_host_tool_gate": (
"TraeX /goal is unavailable; if goal mode is disabled, show the exact "
"Native Goal tools and active Goal context are unavailable; show the exact "
"`/goal <task_body>` text for the user to paste after enabling "
"`[features] goals = true`. Do not claim another host-loop surface "
"`[features] goals = true` if goal mode is disabled. Do not claim another host-loop surface "
"without a verified LoopX adapter."
),
},
"activation_steps": [
"Run the visible-goal prompt JSON command after project state and todos are written.",
"Read task_body from the JSON payload.",
"Set the visible TraeX goal to `/goal <task_body>`; enable `[features] goals = true` first if goal mode is off.",
"Read the current native Goal with get_goal when available, otherwise use the current host-provided Goal context. Tool availability is an observation, not permission to change a Goal.",
"For continuation inside the verified same-thread LoopX Goal/Agent binding, retain the active native Goal and its original objective. Do not call create_goal or replace that objective with task_body. Preserve paused, blocked and completed states; never resume them without user direction.",
"For an authorized fresh start, when the host confirms no native Goal exists, call create_goal with objective=task_body if available, then verify the Goal with get_goal or current host context. If native tools and active Goal context are unavailable, use the missing_host_tool_gate manual /goal path; enable `[features] goals = true` only if goal mode is off.",
"Keep delivery in the visible TUI turn; gate each continuation through LoopX quota should-run and do not switch to hidden headless execution.",
],
"success_criteria": [
"The visible TraeX TUI has `/goal <task_body>` active for this goal.",
"The visible TraeX TUI has a verified active native Goal for this work: retain its original objective on continuation, or verify task_body after fresh activation. A preview is not activation or model adoption.",
"Future goal turns enter through LoopX quota/status/state before delivery work.",
],
}
Expand Down
4 changes: 3 additions & 1 deletion skills/loopx-project/references/start-and-connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ explicit route switch.

Include `--goal-id <STABLE_GOAL_ID>` when known. Codex App automatically reads
the stable ambient `CODEX_THREAD_ID`, while Trae App reads
`TRAECLI_THREAD_ID`; other hosts that expose a stable opaque thread id should
`TRAECLI_THREAD_ID`. TraeX CLI also reads `TRAECLI_THREAD_ID`, with its own
`traex-cli` binding scope; it never reuses a `trae_app` binding merely because
the opaque thread id matches. Other hosts that expose a stable opaque thread id should
pass it as `--thread-id <HOST_THREAD_ID>` on every `/loopx`
invocation. If that thread is already bound, reuse the returned
`--agent-id <REGISTERED_AGENT_ID>` on start, heartbeat, quota, refresh-state,
Expand Down
82 changes: 77 additions & 5 deletions tests/control_plane/test_start_goal_compact_projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,14 +1199,18 @@ def test_cli_codex_app_reuses_ambient_thread_binding(
assert payload["guided_transaction"].get("blocked_by") is None


def test_cli_trae_app_reuses_ambient_thread_binding(
tmp_path: Path, monkeypatch
@pytest.mark.parametrize(
("host_surface", "prompt_flag"),
(("trae_app", "--trae_app"), ("traex-cli", "--runtime-profile generic_cli")),
)
def test_cli_trae_hosts_reuse_ambient_thread_binding(
tmp_path: Path, monkeypatch, host_surface: str, prompt_flag: str
) -> None:
project = _write_connected_project(tmp_path)
registry_path = project / ".loopx" / "registry.json"
registry = json.loads(registry_path.read_text(encoding="utf-8"))
registry["goals"][0]["coordination"]["thread_agent_bindings"] = [
{"thread_id": "trae-thread", "host_surface": "trae_app", "agent_id": AGENT_ID}
{"thread_id": "trae-thread", "host_surface": host_surface, "agent_id": AGENT_ID}
]
registry_path.write_text(json.dumps(registry, indent=2) + "\n", encoding="utf-8")
monkeypatch.setenv("TRAECLI_THREAD_ID", "trae-thread")
Expand All @@ -1224,7 +1228,7 @@ def test_cli_trae_app_reuses_ambient_thread_binding(
"--goal-id",
GOAL_ID,
"--host-surface",
"trae_app",
host_surface,
"--goal-text",
GOAL_TEXT,
]
Expand All @@ -1235,11 +1239,79 @@ def test_cli_trae_app_reuses_ambient_thread_binding(
assert payload["thread_id"] == "trae-thread"
assert payload["agent_id"] == AGENT_ID
assert payload["thread_agent_binding"]["status"] == "bound"
assert "--trae_app" in payload["command_pack"]["commands"][
assert payload["goal_text"] == GOAL_TEXT
assert prompt_flag in payload["command_pack"]["commands"][
"heartbeat_prompt"
]


@pytest.mark.parametrize(
("ambient", "bound_host", "explicit", "conflict", "expected_gate"),
(
("bound-thread", "traex-cli", None, False, None),
("other-thread", "traex-cli", "bound-thread", False, None),
("new-thread", "traex-cli", None, False, "fresh_agent_registration_required"),
(None, "traex-cli", None, False, "thread_binding_selection_required"),
("bound-thread", "trae_app", None, False, "fresh_agent_registration_required"),
("bound-thread", "traex-cli", None, True, "thread_binding_conflict"),
),
)
def test_cli_traex_thread_continuity_preserves_identity_fences(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
ambient: str | None,
bound_host: str,
explicit: str | None,
conflict: bool,
expected_gate: str | None,
) -> None:
project = _write_connected_project(tmp_path)
registry_path = project / ".loopx" / "registry.json"
registry = json.loads(registry_path.read_text(encoding="utf-8"))
coordination = registry["goals"][0]["coordination"]
bindings = [
{"thread_id": "bound-thread", "host_surface": bound_host, "agent_id": AGENT_ID}
]
if conflict:
coordination["registered_agents"].append("conflicting-agent")
bindings.append(
{"thread_id": "bound-thread", "host_surface": bound_host,
"agent_id": "conflicting-agent"}
)
coordination["thread_agent_bindings"] = bindings
registry_path.write_text(json.dumps(registry, indent=2) + "\n", encoding="utf-8")
before = registry_path.read_bytes()
# Another host's ambient id cannot fill a missing TraeX id.
monkeypatch.setenv("CODEX_THREAD_ID", "bound-thread")
if ambient is None:
monkeypatch.delenv("TRAECLI_THREAD_ID", raising=False)
else:
monkeypatch.setenv("TRAECLI_THREAD_ID", ambient)
argv = [
"--format", "json", "start-goal", "--guided", "--project", str(project),
"--goal-id", GOAL_ID, "--host-surface", "traex-cli", "--goal-text", GOAL_TEXT,
]
if explicit is not None:
argv += ["--thread-id", explicit]
output = io.StringIO()
with contextlib.redirect_stdout(output):
exit_code = cli_main(argv)
assert exit_code == 0
payload = json.loads(output.getvalue())
assert payload["goal_text"] == GOAL_TEXT
assert payload.get("thread_id") == (explicit or ambient)
assert registry_path.read_bytes() == before
gate = payload["guided_transaction"].get("identity_selection_gate")
if expected_gate is None:
assert gate is None
assert payload["agent_id"] == AGENT_ID
assert payload["thread_agent_binding"]["status"] == "bound"
else:
assert gate["state"] == expected_gate
assert payload["agent_id"] is None
assert payload["guided_transaction"]["blocked_by"] == "agent_identity_selection"


def test_start_goal_binds_selected_lane_before_todo_writeback(
tmp_path: Path,
) -> None:
Expand Down
15 changes: 10 additions & 5 deletions tests/test_core_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,19 @@ def test_installed_core_identity_help_and_unavailable_workspace(core_python, tmp
assert not list(tmp_path.iterdir())


def test_installed_core_guided_start_identity_and_readback(core_python, tmp_path, monkeypatch):
@pytest.mark.parametrize("host_surface", ("shell", "traex-cli"))
def test_installed_core_guided_start_identity_and_readback(core_python, tmp_path, monkeypatch, host_surface):
from tests.control_plane.canonical_authority_fixture import isolate_sqlite_runtime

isolate_sqlite_runtime(tmp_path, monkeypatch)
if host_surface == "traex-cli":
monkeypatch.setenv("TRAECLI_THREAD_ID", "core-session")
project, runtime = tmp_path / "project", tmp_path / "runtime"
project.mkdir()
prefix = ["--runtime-root", str(runtime), "--format", "json"]
objective = 'Produce an inventory report; preserve "quoted" source names and 总计.'
start = [*prefix, "start-goal", "--guided", "--project", str(project),
"--goal-id", GOAL, "--host-surface", "shell", "--goal-text", objective]
"--goal-id", GOAL, "--host-surface", host_surface, "--goal-text", objective]

def command(*arguments, expected=0):
return json.loads(_run(core_python, list(arguments), project, expected=expected).stdout)
Expand All @@ -166,15 +169,17 @@ def command(*arguments, expected=0):
"--agent-id", AGENT, "--require-new", "--execute", expected=1)
assert collision["ok"] is False and collision["written"] is False
binding = command(*prefix, "bind-agent-thread", "--goal-id", GOAL,
"--agent-id", AGENT, "--thread-id", "core-session", "--host-surface", "shell", "--execute")
"--agent-id", AGENT, "--thread-id", "core-session", "--host-surface", host_surface, "--execute")
assert binding["global_sync"]["ok"] is True
assert binding["registration_readback"]["verified"] is True
resumed = command(*start, "--thread-id", "core-session")
resumed = command(*start) if host_surface == "traex-cli" else command(*start, "--thread-id", "core-session")
assert resumed["agent_id"] == AGENT
assert resumed["project_connection"]["goal_found"] is True
assert "identity_selection_gate" not in resumed["guided_transaction"]
assert resumed["goal_text"] == objective
assert resumed["thread_id"] == "core-session"
pack = command(*prefix, "bootstrap-command-pack", "--project", str(project),
"--goal-id", GOAL, "--agent-id", AGENT, "--host-surface", "shell", "--goal-text", objective)
"--goal-id", GOAL, "--agent-id", AGENT, "--host-surface", host_surface, "--goal-text", objective)
assert pack["goal_start_contract"]["goal_text"] == objective
assert pack["goal_start_contract"]["explicit_invocation_confirms_project_local_state_writes"] is True

Expand Down
1 change: 1 addition & 0 deletions tests/test_host_loop_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ def test_traex_cli_is_an_exact_visible_goal_host_on_the_generic_cli_loop() -> No
assert packet["activation_method"] == "set_visible_goal"
assert packet["host_mutation"]["owner"] == "TraeX CLI TUI"
assert packet["host_mutation"]["host_command"] == "/goal <task_body>"
assert packet["host_mutation"]["preferred_tool"] == "create_goal"
assert packet["host_mutation"]["requires_host_feature_flag"] == (
"[features] goals = true in ~/.trae/traecli.toml"
)
Expand Down
Loading