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
23 changes: 13 additions & 10 deletions benchmark/swe-marathon/agents/codex_loopx_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@
# 而「Only user `/goal resume` reactivates it」——benchmark 里没有 user。
# v2 有 21/45 个任务、113/417 个阶段(27%)以 blocked 收尾。
#
# 三处都用官方 CLI 参数修,不碰 LoopX 渲染出的 goal body,保真度门禁照旧。
# 修复①和②的参数已随上游删除首连 onboarding 门禁而失效:现在无论哪一版,
# bootstrap 都只登记 goal,不再写 onboarding todo,也不再要求
# connection validation / heartbeat 选择。LOOPX_UNGATED 目前只剩写权限声明
# 这一处差异,仍不碰 LoopX 渲染出的 goal body,保真度门禁照旧。
_UNGATED = bool(os.environ.get("LOOPX_UNGATED"))

# ── 三个模式 ────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -157,17 +160,17 @@ class CodexLoopxAgent(CodexGoalAgent):
def _bootstrap_gates(self, cwd: str) -> str:
"""bootstrap 末尾那串门禁/规划相关的参数。

基准两版(v1/v2)用的是抑制方向的组合,第三版反过来。差别只有这一处,
其余(objective、adapter、goal-doc、goal-id)逐字不变。
历史记录(v1/v2 用 `--no-onboarding-scan --codex-app-heartbeat ask`,
v3 用 `--accept-onboarding-agent-todos --begin-autonomous-advance
--codex-app-heartbeat yes`)描述的是 LoopX 当时的首连门禁参数。这些参数
已在上游删除:bootstrap 不再写入任何首连 onboarding todo,也不再有
connection validation / heartbeat 选择项。现在两版只剩写权限声明不同,
objective、adapter、goal-doc、goal-id 仍逐字不变。
"""
if not _UNGATED:
return "--no-onboarding-scan --codex-app-heartbeat ask"
return (
# ① 打开 LoopX 自己的首连扫描与候选 todo 提议,并允许自动推进
"--accept-onboarding-agent-todos --begin-autonomous-advance "
# ② 预授权心跳 + 声明写权限(原来 coordination.write_scope 是空的)
f"--codex-app-heartbeat yes --write-scope {cwd}"
)
return ""
# 声明写权限(原来 coordination.write_scope 是空的)
return f"--write-scope {cwd}"

def _goal_id(self) -> str:
"""本 trial 的 goal id。
Expand Down
2 changes: 1 addition & 1 deletion benchmark/swe-marathon/runtime/modes/run_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def main(argv: list[str] | None = None) -> int:
try:
session.bootstrap(objective)
# 任务必须作为 todo 进 goal —— 闸门按 todo 选工作,只放进 turn 输入的话
# 模型会去推进 onboarding todo,跑满预算却零产出且不报错。
# 模型没有可执行 todo,会跑满预算却零产出且不报错。
session.add_task_todo(task)
except SessionError as exc:
out["error"] = f"bootstrap 失败: {exc}"
Expand Down
20 changes: 7 additions & 13 deletions benchmark/swe-marathon/runtime/modes/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,16 @@ def bootstrap(self, objective: str) -> dict[str, Any]:
"cannot be used because goal has no coordination.registered_agents list",
而它仍然退出 0,很容易被当成渲染成功。

两个 onboarding 开关也不能省。不加的话闸门会一直回
"operator gate blocks gated delivery",should_run 恒为 false——无人值守
环境下没有 operator 去放行,整轮会静默空转出零产物却不报错。
bootstrap 不再写入任何首连 onboarding todo(user gate / 候选 todo /
connection validation 都已删除),所以接 goal 之后闸门里不会再有
需要人工放行的条目,无人值守环境也不会静默空转。
"""

boot = self._run([
"bootstrap",
"--project", ".",
"--goal-id", self.goal_id,
"--objective", objective,
# 把 onboarding 提出的 agent todos 直接写进去,并记录允许自主推进;
# 否则等一个永远不会出现的人工放行。
"--accept-onboarding-agent-todos",
"--begin-autonomous-advance",
# 不让 bootstrap 去问要不要建 Codex App 心跳自动化:本工作区没有真 App。
"--codex-app-heartbeat", "no",
])
self._run([
"configure-goal",
Expand All @@ -128,10 +122,10 @@ def bootstrap(self, objective: str) -> dict[str, Any]:
def add_task_todo(self, task_text: str, *, todo_id: str = "wen-task") -> dict[str, Any]:
"""把任务正文作为一条 P0 agent todo 写进 goal。

这一步不能省,也不能只靠 turn/start 的输入。实测过:只把任务放进 turn
输入、goal 里只有 onboarding todo 时,模型会老老实实按 body 的指示去推进
**onboarding todo**,900 秒里只建了 .loopx/ 和 .codex/,任务文件一个字没改,
而且不报错——闸门放行、Goal 活着、收据干净,看起来一切正常。
这一步不能省,也不能只靠 turn/start 的输入:闸门是按 todo 选工作的,
任务不在 todo 里就不会被选中。实测过只把任务放进 turn 输入的情况,900 秒
里只建了 .loopx/ 和 .codex/,任务文件一个字没改,而且不报错——闸门放行、
Goal 活着、收据干净,看起来一切正常。

闸门是按 todo 选工作的,任务不在 todo 里就不会被选中。
"""
Expand Down
3 changes: 1 addition & 2 deletions benchmark/swe-marathon/runtime/turn/loopx_native_codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,7 @@ def run(name, args):

run("bootstrap", ["bootstrap", "--project", a.project, "--goal-id", a.goal_id,
"--objective", "Complete the software engineering task described "
"in the task file and commit the finished work.",
"--no-onboarding-scan"])
"in the task file and commit the finished work."])
run("configure_goal", ["configure-goal", "--goal-id", a.goal_id,
"--registered-agent", a.agent_id, "--execute"])
prompt = run("heartbeat_prompt",
Expand Down
4 changes: 0 additions & 4 deletions demo/auto_research/demo_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,6 @@ def _seed_visible_demo_control_plane(
max_children=4,
allowed_domains=["auto-research-demo"],
write_scope=_visible_demo_goal_write_scope(preset_context),
onboarding_scan_enabled=False,
accept_onboarding_agent_todos=False,
begin_autonomous_advance=True,
codex_app_heartbeat="no",
force=False,
dry_run=False,
sync_global=False,
Expand Down
2 changes: 0 additions & 2 deletions demo/workspace/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,6 @@ def seed_story(root: Path, story: dict, notice: str) -> dict:
max_children=0,
allowed_domains=[],
write_scope=[],
onboarding_scan_enabled=False,
codex_app_heartbeat="no",
force=False,
dry_run=False,
sync_global=False,
Expand Down
8 changes: 7 additions & 1 deletion docs/book/chapters/05-connect-existing-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
- `loopx doctor` 报告安装可用;
- 项目存在 `.loopx/registry.json`;
- 项目存在 `.codex/goals/<goal-id>/ACTIVE_GOAL_STATE.md`;
- `loopx status` 能显示 active state、当前 Gate 和下一项 Agent Todo;
- `loopx status` 能显示 active state 和当前 frontier;首连不会生成 onboarding todo,
第一个交付 todo 由 Agent 与你确认后写入;
- `.loopx/` 与 `.codex/goals/` 不会进入 Git;
- 再次连接会按精确 `goal_id` 复用已有 Goal,而不是覆盖目标;
- 新接入的执行者使用 fresh `agent_id`,除非用户明确授权 takeover。
Expand Down Expand Up @@ -201,6 +202,11 @@ loopx start-goal \
这个命令生成 guided transaction packet。它默认是预览,不应被理解为已经完成 Todo 写回、Host
激活和 Agent Turn。Agent 或 Host 集成需要按 packet 执行计划、状态写回与启动步骤。

`connect` / `bootstrap` 只登记 Goal 并写入 active state:它不会生成首连 onboarding todo、
owner 决策门禁或 Host loop opt-in 门禁。首连之后状态里没有可执行的 agent todo,第一个交付
todo 由 Agent 或已接入的 domain adapter 写入,避免自动化从生成的 onboarding 队列而不是
调用方自己的工作队列开始。

### 先选择 Goal,再选择 Agent

Guided start 会把两个选择分开:
Expand Down
8 changes: 7 additions & 1 deletion docs/book/en/chapters/05-connect-existing-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ When onboarding is complete:
- `loopx doctor` reports a usable installation;
- `.loopx/registry.json` exists in the project;
- `.codex/goals/<goal-id>/ACTIVE_GOAL_STATE.md` exists;
- `loopx status` can show active state, current Gates, and the next Agent Todo;
- `loopx status` can show the active state and the current frontier; a first connection creates no
onboarding Todo, so the Agent writes the first delivery Todo after you confirm it;
- `.loopx/` and `.codex/goals/` do not enter Git;
- reconnecting reuses the exact existing `goal_id` instead of overwriting the Goal;
- a new executor receives a fresh `agent_id` unless the user explicitly authorizes a takeover.
Expand Down Expand Up @@ -213,6 +214,11 @@ This produces a guided transaction packet. It is a preview, not proof that Todo
or an Agent turn has already happened. The Host integration must execute the planning, state writeback, and
activation described by the packet.

`connect` / `bootstrap` register the Goal and write the active state only: they create no first-connect
onboarding Todo, owner-decision gate, or host-loop opt-in gate. A freshly connected goal therefore has no
executable Agent Todo; the Agent or the connected domain adapter writes the first delivery Todo, so
automation starts from the caller's own work queue instead of a generated onboarding queue.

### Choose the Goal before choosing the Agent

Guided start keeps two decisions separate:
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/interaction-pattern-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,7 @@ todo and the automation drifts into monitor-only no-ops.
**Validation**

- `examples/state-projection-gap-smoke.py`
- `examples/project/onboarding-no-scan-projection-smoke.py`
- `examples/project/first-connect-contract-smoke.py`
- `docs/project-agent-todo-contract.md`

#### IP-006 Checkpointed Scope Mismatch
Expand Down
26 changes: 7 additions & 19 deletions docs/integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,25 +98,13 @@ loopx bootstrap \

`loopx connect` is an alias for the same operation. The command is
safe to rerun: by default it keeps an existing state file and existing registry
entry. If the goal only needs an additional write boundary after connection,
prefer the incremental migration path:

An integration provider that already qualified the project bridge can own the
one-time connection check explicitly:

```bash
loopx connect \
--goal-id project-goal \
--no-onboarding-scan \
--onboarding-connection-validation provider-prevalidated
```

The default remains `agent`, which may create a `loopx check` onboarding Todo
for generic adapters. `provider-prevalidated` records provider ownership in the
registry and omits that agent Todo; it does not run validation, grant tools, or
expand the provider's authority. Use it only when the caller has already
validated the connection. Repository scanning and connection validation remain
separate controls.
entry. Connection registers the goal and writes the active state only: it does
not create first-connect onboarding Todos, owner-decision gates, or host-loop
opt-in gates. The caller — or the connected domain adapter — writes the first
delivery Todo it wants, so an autonomously driven project starts from the
caller's own work queue instead of a generated onboarding queue. If the goal
only needs an additional write boundary after connection, prefer the
incremental migration path:

```bash
loopx configure-goal \
Expand Down
28 changes: 10 additions & 18 deletions docs/operations/new-project-codex-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,8 @@ loopx new-project-prompt \

请你按下面步骤推进,不要停在方案讨论:

重要:`loopx connect` 默认会做一次快速 onboarding scan,基于 git status、
最近 commit、顶层项目信号生成候选 agent todo。接入后不要直接开始 delivery;
先把候选 todo 展示给我,并问我两件事:

1. 接受、编辑或拒绝哪些候选 agent todo;
2. 是否允许你从接受的 todo 开始自主推进。
重要:`loopx connect` 只登记 goal 和 active state,不会生成首连 onboarding todo。
接入后先只读核对状态与目标文档,把第一个交付 todo 的候选写给我确认,再开始 delivery。

0. 先确认当前 shell 能调用 LoopX CLI;如果提示 `loopx`
不在 PATH,运行本机安装脚本再继续:
Expand Down Expand Up @@ -112,18 +108,14 @@ loopx new-project-prompt \

3. 确认 `.loopx/registry.json` 和
`.codex/goals/<STABLE_GOAL_ID>/ACTIVE_GOAL_STATE.md` 已创建或更新。
阅读输出里的 `Onboarding Scan`、`Proposed Onboarding Candidates`、
`Accept Candidate Commands` 和 `Autonomy Choice`。不要让我手动执行这些命令;
你应当用中文简要解释候选 todo,然后询问:
- 接受哪些编号,是否需要改写;
- 是否 `autonomous=yes`,允许你在 quota guard 通过后开始执行第一个接受的
agent todo。
如果我接受候选 todo,用输出里的 `loopx todo add ...` 命令写入
agent todo;如果我允许自主推进,先运行 quota guard,再执行第一个已接受
agent todo。如果我不允许自主推进,只写入接受的 todo 并运行
`loopx refresh-state --goal-id <STABLE_GOAL_ID>`,然后停下来汇报。
如果目标状态包含私有证据,把 `.loopx/` 和 `.codex/goals/`
加入该项目 `.gitignore`。
接入输出里不再有 onboarding 扫描、候选 todo 或自主推进选择项;首连之后状态里
没有可执行的 agent todo。请只读核对目标文档和 registry 的 `execution_profile`,
用中文给出 1-3 个第一个交付 todo 的候选,问我确认后,用
`loopx todo add ...` 写入被接受的条目,再运行
`loopx refresh-state --goal-id <STABLE_GOAL_ID>` 并汇报。在用户确认前不要开始
delivery。
如果目标状态包含私有证据,把 `.loopx/` 和 `.codex/goals/`
加入该项目 `.gitignore`。
`loopx connect` 默认会同步到共享全局 registry;不要手动编辑其他
项目的 registry。
接入后检查 registry 里的 `execution_profile`:它是本项目后续 heartbeat /
Expand Down
32 changes: 26 additions & 6 deletions examples/agent-diagnose-packet-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def assert_diagnose_markdown_separates_status_and_packet_goal_counts() -> None:
assert "contract_warnings_truncated: total=4" in markdown, markdown


def bootstrap_project(project: Path, runtime: Path, goal_id: str, *, onboarding: bool) -> dict:
def bootstrap_project(project: Path, runtime: Path, goal_id: str) -> dict:
args = [
"--runtime-root",
str(runtime),
Expand All @@ -164,8 +164,6 @@ def bootstrap_project(project: Path, runtime: Path, goal_id: str, *, onboarding:
"diagnose_fixture_v0",
"--no-global-sync",
]
if not onboarding:
args.append("--no-onboarding-scan")
return run_cli(*args)


Expand Down Expand Up @@ -316,7 +314,7 @@ def main() -> int:
runtime = root / "runtime"

ready_project = write_project(root, "ready-project")
bootstrap_project(ready_project, runtime, GOAL_ID, onboarding=False)
bootstrap_project(ready_project, runtime, GOAL_ID)
registry = ready_project / ".loopx" / "registry.json"
added = run_cli(
"--registry",
Expand Down Expand Up @@ -380,15 +378,37 @@ def main() -> int:

gated_project = write_project(root, "gated-project")
gated_goal_id = "diagnose-smoke-gated"
bootstrap_project(gated_project, runtime, gated_goal_id, onboarding=True)
bootstrap_project(gated_project, runtime, gated_goal_id)
gated_registry = gated_project / ".loopx" / "registry.json"
# ``connect`` no longer seeds an owner gate, so the fixture writes the
# user gate it wants to diagnose by itself.
user_gate_text = "[P1] Confirm the release window before autonomous delivery."
run_cli(
"--registry",
str(gated_registry),
"todo",
"add",
"--goal-id",
gated_goal_id,
"--role",
"user",
"--text",
user_gate_text,
"--task-class",
"user_gate",
"--action-kind",
"owner_decision",
"--global-gate",
"--execute",
)
gated_packet = run_cli("--registry", str(gated_registry), "diagnose", "--goal-id", gated_goal_id)
gated_selected = gated_packet["selected"]
assert gated_selected["machine_signal"] == "user_or_controller_attention", gated_selected
assert gated_selected["todo_evidence"]["user_open_count"] == 1, gated_selected
assert gated_selected["quota_signals"]["action_required"] is True, gated_selected
assert gated_selected["quota_signals"]["open_count"] == 1, gated_selected
assert "autonomous=yes/no" in str(gated_selected["user_question"]), gated_selected
assert user_gate_text in str(gated_selected["user_question"]), gated_selected
assert gated_selected["recommended_action"] == user_gate_text, gated_selected
assert "can_self_drive" not in gated_selected, gated_selected

scoped_registry = write_agent_scoped_registry(root, runtime)
Expand Down
3 changes: 0 additions & 3 deletions examples/benchmark-native-goal-installed-profile-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,6 @@ def main() -> int:
"read_only_project_map_v0",
"--adapter-status",
"connected-read-only",
"--no-onboarding-scan",
"--codex-app-heartbeat",
"no",
"--no-global-sync",
)
_run_profile_cli(
Expand Down
4 changes: 1 addition & 3 deletions examples/bootstrap-command-pack-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ def test_missing_project_stops_before_mutation() -> None:
assert isinstance(next_step, dict)
assert next_step["requires_user_confirmation"] is True
assert "--dry-run" in str(next_step["dry_run_command"])
assert "--codex-app-heartbeat ask" in str(next_step["dry_run_command"])
assert "--dry-run" not in str(next_step["after_confirmation_command"])
assert "/loopx-summary-all" not in json.dumps(payload)

Expand Down Expand Up @@ -223,7 +222,7 @@ def test_goal_text_invocation_plans_ranked_todos_before_activation() -> None:
assert "--objective 'Ship the lightweight issue triage workflow'" in str(
next_step["connect_command_if_needed"]
)
assert "--no-onboarding-scan" in str(next_step["connect_command_if_needed"])
assert "--no-onboarding-scan" not in str(next_step["connect_command_if_needed"])

goal_start = payload["goal_start_contract"]
assert isinstance(goal_start, dict)
Expand Down Expand Up @@ -758,7 +757,6 @@ def test_start_goal_guided_derives_display_name_from_goal_text() -> None:
"derived-display-goal",
"--objective",
"修复 scheduler state path 覆盖问题",
"--no-onboarding-scan",
"--no-global-sync",
)
registry = json.loads(
Expand Down
10 changes: 5 additions & 5 deletions examples/canary/catalog-planner-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def assert_profiles_come_from_catalog_matrix() -> None:
"product-entry-workflows",
"cross-runtime-impl-review-demo",
"host-command-entry",
"new-user-onboarding-lifecycle",
"first-connect-contract",
"runtime-connector-catalog",
"frontstage-rollout",
"auto-research-demo",
Expand Down Expand Up @@ -678,16 +678,16 @@ def assert_pr_release_and_refactor_profiles_select() -> None:
"loopx/contract.py",
],
surfaces=[
"new user onboarding no-onboarding-scan state projection gap start-goal"
"new user onboarding first connect contract state projection gap start-goal"
],
)
onboarding_profiles = {
profile["id"]: profile for profile in onboarding_payload["domain_profiles"]
}
assert "new-user-onboarding-lifecycle" in onboarding_profiles, onboarding_payload
onboarding_profile = onboarding_profiles["new-user-onboarding-lifecycle"]
assert "first-connect-contract" in onboarding_profiles, onboarding_payload
onboarding_profile = onboarding_profiles["first-connect-contract"]
assert [check["command"] for check in onboarding_profile["checks"]] == [
"python3 examples/project/onboarding-no-scan-projection-smoke.py"
"python3 examples/project/first-connect-contract-smoke.py"
], onboarding_profile
assert all(check["tier"] == "default" for check in onboarding_profile["checks"])
assert onboarding_profile["deep_checks_available"] is False, onboarding_profile
Expand Down
Loading