Skip to content

feat(goal): 增加可恢复的 headless 单目标 CLI - #549

Merged
deepcoldy merged 1 commit into
deepcoldy:masterfrom
Ghost-LZW:feat/headless-goal-host-api
Jul 26, 2026
Merged

feat(goal): 增加可恢复的 headless 单目标 CLI#549
deepcoldy merged 1 commit into
deepcoldy:masterfrom
Ghost-LZW:feat/headless-goal-host-api

Conversation

@Ghost-LZW

@Ghost-LZW Ghost-LZW commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

为什么值得进入 botmux

botmux 已有 v3 的真实 ephemeral worker、沙箱/enforcement、append-only journal、drive lease、worker fence 与 manifest validator,但程序化运行一个单独 goal 仍只能手写 DAG,再解析面向人的 CLI 输出。

本 PR 把这些现有能力收束成一个当天可用的本机产品入口:

botmux goal run "检查仓库并生成发布说明" \
  --bot my-codex \
  --working-dir ~/projects/app \
  --run-id release-notes-2026-07-22 \
  --timeout 900 \
  --json

它可直接服务本机脚本、CI、cron 和人工触发自动化,不依赖、也不感知任何外部项目或特定 controller。

本 PR 做什么

  • 新增 botmux goal run:位置参数、--goal-file--stdin 三种 goal 输入。
  • 走现有 v3 单节点 DAG + 真实 createEphemeralPool,复用既有沙箱/enforcement;没有第二套 planner、scheduler 或 mutable ledger。
  • --json stdout 只写一份 botmux.goal-run-result/v1 终态 JSON,日志走 stderr;失败也有结构化 error.code/message
  • 稳定退出码:0 succeeded、10 failed、11 blocked、12 cancelled、13 runId / active-driver conflict、14 usage / startup error。
  • 可选 --run-id 是幂等键:
    • 已终态:从 durable journal + validated manifest 原样重放,零 spawn,且不依赖当前 bots.json
    • live drive lock:按 PID + process-start/boot identity 判活,返回独立冲突码,零 spawn;
    • dead lock + 非终态 journal:复用 v3 recovery/worker fence re-drive,新建 attempt,不覆盖旧 attempt。
  • 将 canonical CLI 请求作为 goal.request.json 精确字节 artifact 固定进既有 run.json envelope;DAG 与 secret-free bot snapshot 继续固定解析后的 bot/cwd/model/sandbox。不同 goal、bot selector、cwd 或 timeout 不能复用同一 runId。
  • workingDir 在首次授权时展开 ~ 并冻结为绝对路径;从另一 cwd 重驱不会静默跑进另一个仓库。
  • SIGINT、SIGTERM、--timeout 都通过既有 cancelSignal → durable runCancelRequested → worker drain → runCancelled 路径,终态落盘后才向 stdout ack。
  • artifacts 只来自既有 manifest validator;runDirectory 明示 informative-only,不把私有目录布局变成 API。
  • usage/cost 采不到时不输出字段,不用 0 冒充完整数据。

明确不做什么

这不是 daemon / 远程 Host API,也没有 UDS/HTTP server、事件流、controller lease 字段、跨机器 fencing 或 discovery isolation profile。上述能力若需要,将分别走 RFC / 安全评审 / Linux E2E,不在本 PR 预埋不可运行的脚手架。

因此本 PR 也不再包含旧草案中的:

  • agent-sidecar 与第二套 durable ledger;
  • taskId/nodeId/leaseEpoch/leaseExpiresAt 等外部 controller 专用 wire 字段;
  • REAL_RUNS_DISABLED 测试门;
  • 未接线的 discovery profile / 临时 CI;
  • 任何 Motivation import、命名或叙事。

本机操作者运行 goal run 的信任模型与直接运行 botmux 相同,并且不会绕过现有 v3 沙箱/enforcement 路径。

可靠性合同

  • terminal-before-ack:journal 已 durable terminal 后才同步写最终 JSON。
  • create-or-attach(无 daemon):terminal replay / live conflict / dead-driver re-drive 三分明确。
  • crash recovery:Linux 集成测试真实 SIGKILL 首个 driver,再以同一 runId 恢复为 attempts/002
  • cancellation:SIGINT、SIGTERM、timeout 都验证最后一个 journal event 是 runCancelled
  • concurrency:同 runId 双并发调用断言恰好一次 runNode
  • request integrity:goal.request.json exact bytes 与 SHA-256 进入 immutable run.json,篡改 fail-closed。
  • machine output:subprocess wire 测试断言 stdout 恰好等于一个 JSON 文档。

验证

基于最新 origin/master@f4cc4f56

  • pnpm build:通过
  • pnpm exec tsc --noEmit:通过
  • feature + v3 回归:80 passed,Linux-only kill -9 1 skipped on macOS
    • test/v3-goal-cli.test.ts
    • test/v3-run-envelope.test.ts
    • test/cli-root-help.test.ts
    • test/v3-runtime.test.ts
    • test/v3-cancel-runtime.test.ts
  • 全量 pnpm test:622 files passed / 9811 tests passed / 23 skipped
  • git diff --check origin/master...HEAD:通过

Review

Bob 已按 B1–B6 逐项复核,并对最新 dc40e275 的 absolute workingDir 增量复审通过;当前结论 approved,可以转 Ready。

非阻塞 follow-up:

  1. goal run --help 目前走结构化 USAGE / exit 14,后续可补人类 help / exit 0。
  2. RUN_ACTIVE 当前通过 file-lock timeout 文案分类,后续宜给 withFileLock 增加 typed error。
  3. 单节点 v1 的 frozen snapshot resolver 有首项回退;若未来多节点化,应改为 selector 严格匹配。

后续(不阻塞本 PR)

若未来另提 daemon API RFC,应继承已经验证过的三条机制:过期 dead predecessor 的 cancel-fold 接管、per-task admission 串行化、以及受前任/自身双租约边界钳制的退避。跨机器 discovery isolation 另提 PR,并以 Linux 真实正/负 E2E 为合入门。

@Ghost-LZW
Ghost-LZW requested a review from deepcoldy as a code owner July 22, 2026 00:58
@Ghost-LZW
Ghost-LZW marked this pull request as draft July 22, 2026 01:28
@Ghost-LZW
Ghost-LZW force-pushed the feat/headless-goal-host-api branch from 4f75759 to 1f2763c Compare July 22, 2026 01:56
@Ghost-LZW Ghost-LZW changed the title feat(goal-runtime): 增加通用 headless goal Host API feat(goal): 增加可恢复的 headless 单目标 CLI Jul 22, 2026
@Ghost-LZW
Ghost-LZW force-pushed the feat/headless-goal-host-api branch from 1f2763c to dc40e27 Compare July 22, 2026 01:59
@Ghost-LZW
Ghost-LZW marked this pull request as ready for review July 22, 2026 02:03
@deepcoldy

Copy link
Copy Markdown
Owner

Review 结论:Approved ✅ 已验证可合并

在最新 origin/master@b93f88e5 上做了合并后的实测(PR 分支落后 master 166 个 commit,故不能只信 PR 描述里基于 f4cc4f56 的验证结果)。

一、合并可行性

  • git merge-tree0 冲突
  • 本地把 origin/master 合入 PR 分支后:
    • pnpm build
    • pnpm exec tsc --noEmit ✅ (exit 0)
    • PR 相关用例 v3-goal-cli / v3-run-envelope / cli-root-help / v3-runtime / v3-cancel-runtime81 passed
    • 全量 pnpm test10704 passed / 10 failed

那 10 个 failed 在未打本 PR 的纯 origin/master 上跑,结果完全一致v3-distillation-runner ×6、scheduler ×2、schedule-card-model ×1、fs-policy-bwrap.e2e ×1)——属于 master 既有失败(时区/沙箱环境相关),与本 PR 无关。

二、Breaking change 复核(申晗关注点)

结论:无 breaking change

  1. run envelope schema 向后兼容artifacts.goalRequestauthorization.goalRequestSha256 都是 .optional(),既有 v3 run 写出的 run.json(两者皆缺)仍然通过 .strict() 校验;新增的 superRefine 只在 local_cli 分支断言"两者同在或同缺",不影响历史 envelope。
  2. authorizeManualCliRun 新增拒绝路径不伤存量v3 run 附加到带 goalRequest 的 runId 会抛 conflict——但带该字段的 run 目录只可能由合并后的 goal run 创建,历史 run 目录不受影响。
  3. CLI 顶层命令 goal 无冲突src/cli.ts 现有 switch 无同名 case,也不与 v3 / workflow 命名空间重叠。
  4. 退出码 10-14 是新命令私有约定,不改任何现有命令的退出码。
  5. manifest / journal / worker fence 全部复用既有路径,没有第二套 schema 或 ledger。

三、安全 & 仓库红线

  • 未引入任何内网域名 / PSM / 密钥,符合公开仓红线 ✅
  • goal.request.jsonbots.snapshot.jsonmode: 0o600
  • workingDir 在首次授权时 expandHomePath + resolve 冻结为绝对路径,崩溃后从别的 cwd 重驱不会误跑进另一个仓库 ✅
  • 信任边界 = 本机操作者直接跑 botmux,不是远程执行 API ✅
  • 不触及隧道控制协议,无需 platform 侧联动 ✅

四、冒烟

$ node dist/cli.js goal run --json          → exit 14  USAGE
$ node dist/cli.js goal run x --timeout=abc → exit 14  USAGE
$ node dist/cli.js goal bogus --json        → exit 14  USAGE

stdout 均为恰好一份 JSON 文档,符合机器合同。

五、非阻塞 follow-up(不拦本 PR)

  1. goal run --help 目前走 USAGE 分支 + exit 14,宜改为人类可读 help + exit 0。
  2. --base-dir 已实现但 README / help 文本未提及。
  3. --goal-file / --stdin 的用例覆盖。
  4. resolveFrozenSnapshotget(selector ?? '') 链在 selector 为 undefined 时必然两次 miss 才 fallback,可简化为 selector ? ... : firstSnapshot
  5. RUN_ACTIVE 靠 file-lock timeout 文案分类,宜给 withFileLock 加 typed error。

以上均为改进项,建议后续小 PR 处理。现予合并。

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在最新 origin/master 上做合并后实测:0 冲突、tsc 通过、PR 相关 81 用例通过、全量 10704 passed(10 个 failed 与纯 master 基线完全一致,属既有失败)。Breaking change 复核:run envelope 新增字段均 optional、CLI 命令名无冲突、退出码为新命令私有约定,向后兼容。Approved。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants