122 KiB
24 domainCheck Node Agent 协议与 Release Hub 设计
如果当前已经进入“把方案真正落地到上线前收口”的阶段,建议配套阅读:
docs/25_domainCheck_海外单脑控制面上线收口总表.mddocs/schemas/ops_driver_contract.mddocs/schemas/ops_stack_diagnosis_contract.md
一、Node Agent 为什么是核心
终局架构里,大陆节点不应该再是“需要人工登录的机器”,而应该是:
受控执行节点
每个节点通过 domaincheck-node-agent 接入海外控制面,承担:
- 注册
- 心跳
- 拉取任务
- 执行任务
- 回传结果
- 回传日志
- 回传诊断包
这样后台按钮和 Codex 都不用再直接碰节点 shell。
补一个很关键的落地约束:
- OpsCenter 顶部的“总检入口”现在应正式优先消费
GET /api/v1/ops/go-live-summary GET /api/v1/ops/stack-diagnosis- 保留为更细的可解释总检层
- 用于继续下钻
issues / operator_decision / next_actions / recommended_commands
- CLI 总检脚本也应优先消费同一个 contract
- 海外 Codex 驾驶员后续默认也先看这一份总检结果
也就是说,Node Agent、Release Hub、页面按钮、Codex 驾驶员,不再各自拼一套“当前现场判断”,而是统一先看:
go-live summary
然后再根据 stack-diagnosis / overview / release launchpad 继续下钻。
这里还要再固定一个新口径,避免页面和运维动作继续混用“可联调”和“可发版”:
go_live_status- 回答“当前是否已经进入统一收口、联调、复核车道”
- 允许出现
attention
publish_status / publish_ready- 回答“当前是否已经满足正式发版门禁”
- 必须明确区分
blockedattentionready
这里再补一条已经收口成代码规则、后续不要再放松的判断:
- 如果目标节点的 Node Agent
delivery_queue仍然存在dead_letter- 这不是普通 warning
- 对
publish_status必须按blocked处理 - 对
default_rollout_gate也必须按阻断处理
- 如果只是
retrying- 可以继续保留为
warning / attention - 但不应该再被当成“完全干净的发布现场”
- 可以继续保留为
原因很简单:
dead_letter说明控制面到节点的执行回执链路已经出现语义失败- 这时候页面如果还显示“可发布”,就会制造最危险的假阳性
- 所以
go_live_status可以是attention - 但
publish_status必须更严格,直接阻断正式发版
这里还要补一条已经落成代码的事件语义,后续不要再退回“任务成功就算一切成功”的旧口径:
- 如果 Node Agent 在执行
jobs/{job_id}/start时,开始回执投递失败- 允许节点继续本地执行
- 不允许因为这一次投递失败就直接中断真实任务
- 但控制面必须把这类现场显性化为单独问题,而不是静默吞掉
- activity stream 中对应
ops_job应升级为attention - 保留原始
job_status - 并暴露
start_delivery_statestart_delivery_errorsource_focus_ref
- activity stream 中对应
ops overview / go-live summary- 也必须把它当成正式关注项
- 不能只在活动流里可见、但首页总检仍显示“可直接上线”
- 当前口径应是
go_live_status = attentionpublish_status = attention- 默认下一步指向
focus_latest_job_events
stack-diagnosis- 也必须把这类现场提升为可操作 issue
- 默认建议动作应该回到
focus_latest_job_events
这里再固定一个动作层约束,避免后面 UI / CLI / Codex 又各自猜入口:
- 如果活动焦点已经明确落到
source_focus_ref.kind = ops_job_event- 或者问题本身就是“开始回执异常”
- 那么默认焦点动作不应继续使用泛化的
focus_activity_item - 而应直接升级为
focus_latest_job_events
原因是:
focus_activity_item适合“先进入这条活动对应的详情”focus_latest_job_events适合“问题已经明确在 job event 级别”- 对开始回执异常这类问题,继续停在 activity 级别只会多一跳,降低定位效率
原因也很直接:
- 这类问题不代表节点没执行
- 但代表控制面和节点现场之间已经出现“开始态失联”
- 如果不显性化,页面会以为“任务没问题,只是还没刷新”
- 实际上这是自动化链路一致性已经开始打折的早期信号
也就是说:
- “总检入口”不只告诉你系统是不是在健康收口
- 还必须同时告诉你“现在能不能正式发布”
后续页面、CLI、Codex、按钮动作,都应该优先消费这两个层级,而不是再各自推导一套“看起来差不多能上”的临时判断。
这里再补一个已经落地的收口原则:
GET /api/v1/ops/stack-diagnosis现在不只返回传统的issuesrecommended_actionsnext_step
- 还会正式返回
diagnosis.operator_decisiondiagnosis.next_actionsdiagnosis.recommended_commands
这三层的定位要固定下来:
operator_decision先回答“当前主处理车道是什么” 例如:observability / node_handover / release / ops_jobs / steadynext_actions直接给出主动作和复核动作的下一跳命令recommended_commands把节点现场日志、接管、Release Hub、driver-resolve 等常用 drill-down 统一收口成可复用命令集
这样页面顶部“当前主决策”卡、CLI 的 check_ops_center_stack.sh、海外 Codex 驾驶员,就都不该再各自重新推导“当前应该先看日志还是先接管还是先看发布门禁”,而是优先共享后端总检 contract。
这里再补一条已经收口成正式操作约束、后续不要再退回去的口径:
- 如果总检已经识别出
runtime_build_schema_staleruntime_route_surface_incompleteruntime_build_info_unavailable
- 页面、CLI、Codex 驾驶员的第一反应不应该再是“给我一条
systemctl restart domaincheck-api” - 而应该统一提升为标准恢复入口
runtime-refresh-recovergo-live-recover
这两个入口的语义要固定:
runtime-refresh-recover- 回答“当前运行中的 API 是否还是旧 schema / 旧路由面”
- 给出固定恢复链
- 再输出下一跳复检命令
go-live-recover- 把
runtime-refresh-recover stack-diagnosisstack-nextgo-live-checkdoctor这条联调收口链直接串起来
- 把
也就是说:
- 原子 systemd 重启仍然只是底层执行事实
- 对页面按钮、CLI、Codex 来说,正式入口必须是组合恢复动作
- 这样后续总检摘要、主决策卡、推荐命令集,才不会再次退化成“看见 schema 漂移就手工重启”
再往前一层,现在还应该固定:
GET /api/v1/ops/driver-feed- 返回
go_live_summary
- 返回
GET /api/v1/ops/driver-feed- 继续返回
automation_coverage
- 继续返回
GET /api/v1/ops/codex-brief- 返回
go_live_summary
- 返回
GET /api/v1/ops/codex-brief- 继续返回
automation_coverage
- 继续返回
也就是说:
- 页面顶部先看
go-live-summary - Codex 驾驶员先看
codex-brief.go_live_summary - CLI 总检先看
go-live-check - 真正需要解释“为什么阻断 / 下一步为什么是这个动作”时,再回退到
stack-diagnosis
同时还要固定一条“自动化覆盖率”口径,避免后续页面、CLI、Codex 又各自推导一套“现在已经自动化到什么程度”:
driver_feed.automation_coveragecodex_brief.automation_coverage
至少需要稳定包含:
automation_level_countsrecommendation_countsexecutor_kind_countssafe_auto_totalguarded_auto_totalmixed_totalui_only_totalblocked_totalpreview_only_totalbackend_handled_totalexecution_ready_totalhuman_dependency_totallaunch_statuslaunch_ready
其中语义固定为:
preview_only_total统计那些已经后端化、但本质仍属于“只读预览 / 聚焦 / 审阅”的动作backend_handled_total统计已经脱离人工页面点击、可以直接走后端 contract 的动作execution_ready_total统计auto_execute + confirm_then_executehuman_dependency_total统计resolve_first + open_ui + blockedlaunch_status作为“自动化层面的收口判断” 取值固定为ready / attention / blockedlaunch_ready只回答“从自动化执行角度看,当前是否已经接近无人值守可上线”
这样才是完整的:
- 一份稳定收口摘要
- 一份可解释总检
- 一组可执行下钻命令
二、当前已经落下的第一版协议骨架
当前后端已经补出这些接口骨架:
POST /api/v1/ops/agent/tokensPOST /api/v1/ops/agent/bootstrap-planPOST /api/v1/ops/agent/registerPOST /api/v1/ops/agent/heartbeatPOST /api/v1/ops/agent/pullPOST /api/v1/ops/agent/jobs/{job_id}/startPOST /api/v1/ops/agent/jobs/{job_id}/completePOST /api/v1/ops/agent/jobs/{job_id}/events
这几类接口已经足够表达“节点是怎么接控制面的”。
当前仓库里也已经补出第一版 agent 运行骨架:
domain-api/app/node_agent.pydomain-api/deploy/systemd/domain-node-agent.servicedomain-api/deploy/multi-region/build_node_agent_bootstrap_plan.shdomain-api/deploy/multi-region/templates/domaincheck-node-agent.env.example
也就是说,这已经不是纯文档方案,而是开始具备正式执行器入口了。
另外现在控制面已经不只是“签发一枚 token”,而是可以直接生成完整接入方案:
- 后端通过
POST /api/v1/ops/agent/bootstrap-plan - 一次返回:
- token
- env 内容
- 安装命令块
- 启动检查命令
这一步很关键,因为它把“接管新节点”从零散知识点,收敛成了控制面里的标准对象。
控制面页面现在也已经补上了第一版“可视化接管态”:
GET /api/v1/ops/nodes不再只返回“已纳管节点”,也会把“集群可见但未纳管”的节点一起带回来GET /api/v1/ops/nodes/{node_code}/handover可以直接拿到单节点接管详情、阻断项、下一步建议和 bootstrap 默认参数POST /api/v1/ops/nodes/{node_code}/handover/bootstrap-plan可以按节点当前上下文直接生成新的 bootstrap 方案,而不是让页面或脚本自己再拼node_region / node_roleGET /api/v1/ops/nodes/{node_code}/onboarding现在除了返回onboarding_stage / acceptance / recommended_actions,还应返回统一的recovery_decisionPOST /api/v1/ops/nodes/{node_code}/onboarding/recovery/preview应作为node-recover的正式 preview 入口POST /api/v1/ops/nodes/{node_code}/onboarding/recovery/execute应作为node-recover confirm的正式 execute 入口- 节点会被明确区分为:
已接管仅运行态在线待接入心跳过期Token 过期 / 已禁用
这意味着海外控制面第一次真正具备了“先看见,再接管”的统一入口,而不是只有接入完成后才能看见节点。
这里补一个非常关键的收口原则:
recovery_decision必须由后端统一生成- 前端、CLI、Codex 驾驶员不再各自重新推导“下一步该 bootstrap 还是该 acceptance”
- 它至少要固定返回:
actionlabelsummarycommand_hintwindowstage_codeacceptance_status_code
当前口径应固定为:
- 节点仍处于
pending_bootstrapruntime_onlystaleagent_pending- 以及其他接入中状态
时,优先收口为
bootstrap_run
- 节点真正进入
acceptance_ready- 或 Agent 已在线
时,才切到
run_acceptance
这样 node-recover、OpsCenter“自动收口建议”、海外 Codex 驾驶员三边看到的恢复结论才会完全一致。
这里还要补一条很关键的执行约束,避免页面、CLI、Codex 后面又各自补出“双发一次”的隐藏 bug:
node-recover- preview 时优先命中
onboarding/recovery/preview - confirm 时优先命中
onboarding/recovery/execute
- preview 时优先命中
- 如果控制面已经成功命中正式
recovery/execute- 就表示后端已经完成“选择动作 + 发起执行”
- CLI / 页面 / Codex 不允许再额外补跑一次
node-bootstrap-run- 或
node-acceptance-run
- 只有在 recovery endpoint 不存在、退回兼容视图时
- CLI 才可以根据
recovery_decision / stage_code / acceptance_status_code再本地降级决定下一跳
- CLI 才可以根据
也就是说,正式协议下:
- 动作选择权在后端 recovery endpoint
- CLI 和页面只是展示恢复结论与执行回执
- 兼容模式下才允许本地推断
这样才能避免后续接管链路出现:
- preview 看的是一套
- execute 走的是另一套
- CLI 还再补跑一次底层动作
一旦出现这种双发结构,节点接管、验收、发布回执都会失真,这在上线期是不能接受的。
接管通过后,控制面也不应该只是停在“状态已通过”这一步,而是要立刻进入正式运维动作:
- 单节点执行标准巡检
- 批量对“真正参与检测节点”发起巡检
- 批量对“在线未参与节点”发起巡检
- 节点定向进入 Release / Rollout
其中“标准巡检”已经统一成固定序列:
health.snapshotlogs.collectdiagnostics.collect
后续无论是 Codex 驾驶员还是后台按钮,都应该优先复用这条标准序列,而不是再回到临时 shell 排障。
在控制面页面上,这些状态不应该只是文字摘要,还应该继续收口成“驾驶建议卡”:
- 如果存在有效执行节点但尚未接管,优先进入接管动作
- 如果存在参与检测节点但现场日志还不可见,优先开启关键日志回传
- 如果存在真正参与检测节点,优先执行标准巡检
- 如果存在在线未参与节点,优先排查接单/待命问题
- 当接管和巡检链路稳定后,再推进 Release / Rollout
这样 Node Agent 协议、Ops Job 模型和页面按钮才会是同一个体系,而不是三套彼此分裂的运维逻辑。
这里的驾驶动作命名也要尽量收敛成通用语义,而不是绑定页面分组:
run_standard_inspectionopen_worker_logsopen_diagnostics
这样同一套动作才能在:
- 驾驶建议卡
- 后台按钮
- Codex 自动驾驶
- 后续自动化编排
之间稳定复用。
再进一步,为了避免前端按钮、Codex 驾驶员、临时脚本各自维护一套“多步动作串联逻辑”,控制面里还需要再上一层:
ops playbook
它的职责不是直接执行 shell,而是把一个高层运维意图展开为一组标准 ops jobs。
例如:
inspection.standardhealth.snapshotlogs.collect(worker)diagnostics.collect
scene.logs.keylogs.collect(worker, 120)
scene.logs.fulllogs.collect(worker, 300)diagnostics.collect(300)
这样:
- 后端推荐的是 playbook
- 前端按钮点的是 playbook
- Codex 驾驶员调用的也是 playbook
- 真正落库和回执的仍然是
ops jobs
整个模型会更稳定,也更适合后面持续扩机器。
并且这层已经开始真正进入页面主流程:
- OpsCenter 已能列出 playbook
- OpsCenter 已能预览 playbook 展开结果
- “标准巡检”已优先直接调用
inspection.standard - “接管后一键验收”已优先直接调用
onboarding.acceptance
也就是说,控制面 UI、Codex 驾驶员、后端编排这三层终于开始共用同一套多步动作模型。
同样,OpsCenter 不应该只会发任务,还要能直接承接执行现场:
- 把
participating_nodes和non_participating_nodes一起并入运维视图 - 把
log_sync.enabled / mode / source_nodes / last_line一起并入运维视图 - 并允许控制面直接切换远端日志回传模式
这样 Codex 驾驶员和人工按钮都会在同一页看到:
- 谁正在跑
- 谁在线但没跑
- 日志有没有回来
- 要不要临时切到全量回传
并且这层不能只停留在页面按钮,还要提供固定 drill-down 命令:
bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log <mainland_base_url> <node_code> [limit] [key|full]bash domain-api/deploy/multi-region/check_node_scene_log.sh <mainland_base_url> <node_code> [limit] [key|full]
这样海外控制面、CLI 和 Codex 驾驶员在看到“参与检测但缺样本”时,不需要再人工猜下一步看哪台机器,而是直接按节点下钻现场日志。
这里再补一个已经落地的恢复流原则:
- 当
stack-diagnosis/ops overview判断为remote_log_sync_disabled- 或“已有参与节点但现场日志仍不可见”
- CLI、页面按钮、Codex 驾驶员都不应该再只给“去看日志”这种观察型建议
- 而应该优先给出:
log-sync-recover
也就是:
- 先执行
enable_log_sync_key或enable_log_sync_full - 再自动复核
execution_scene.log_sync - 再自动给出下一跳 drill-down
当前海外单入口已经有这一条组合命令:
bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-recover
bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-recover http://127.0.0.1:8100 full confirm cli/ops
它不是“额外的便捷脚本”,而是终局驾驶 contract 的一个关键落地点:
- 页面按钮可直接复用
- Codex 驾驶员可直接复用
stack-diagnosis / doctor / stack-next也应该优先推荐它
这样“恢复可见性”就不再是人工脑补的一串步骤,而是正式的一跳动作。
Node Agent 接管链路也应采用同样原则:
- 当
stack-diagnosis判断主车道已经进入node_handover - 或发现
managed_nodes_agent_pending - 不应该只给出零散的
node-handovernode-bootstrap-plandoctor
- 而应该优先给出一条组合恢复流:
agent-gap-recover
它的目标不是“替代真正的节点落地”,而是把海外控制面需要做的这几步先标准化:
- 识别首个接管缺口节点
- 输出该节点当前 handover 状态
- 生成该节点 bootstrap plan
- 给出后续检查命令
当前海外单入口已经支持:
bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-check
bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-recover
bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-export
而且这条链路也已经具备版本兼容能力:
- 如果当前控制面 API 还没有节点级
/handover- 退回
stack-diagnosis
- 退回
- 如果当前控制面 API 还没有节点级
/handover/bootstrap-plan- 退回通用
/ops/agent/bootstrap-plan
- 退回通用
这样“节点接管”就不再只是页面里的一个弹窗动作,而是正式进入海外单入口、CLI 总检、Codex 驾驶员都可复用的统一恢复模型。
其中:
agent-gap-recover- 负责把首个缺口节点的状态、bootstrap plan、下一跳建议串成一条恢复流
agent-gap-export- 负责把首个缺口节点的接管材料直接导出成目录
- 默认应至少包含:
stack-first-gap-handoveragent-gap-checknode-onboardingnode-bootstrap-plannode-bootstrap-previewnode-acceptance-planmanifest.json
这样海外控制面就不只是“告诉你下一步做什么”,而是可以直接产出一份可执行、可交接、可归档的节点接管包。
另外,node-bootstrap-plan 的 condensed summary 还应该主动区分两类情况:
- 仓库代码本身还不支持最新 bootstrap 字段
- 仓库代码已经支持,但运行中的 API 还没重启到最新版本
后一类在联调里非常常见,所以 summary 必须直接给出:
repo_capability.supports_install_command_blockrepo_capability.runtime_may_need_restartrecommended_commands.restart_api_if_runtime_stale
这样一眼就能判断,这次缺口到底是“代码没写到位”,还是“代码已经在仓库里,但线上进程还是旧的”。
这个判断还必须继续上提到总检 contract:
- 一旦
stack-diagnosis识别出runtime_build_schema_stale - 默认
next_step.action_code- 必须优先收敛为
api-restart
- 必须优先收敛为
diagnosis.operator_decision.lane- 必须优先收敛为
runtime_recovery
- 必须优先收敛为
因为这类问题的本质不是“节点还没接管”,而是“控制面 API 还是旧 schema / 旧路由面”。
如果这里不先做 API 重启,而是继续把人带去执行 bootstrap_run / fix_managed_nodes
- 只会让真正问题继续被掩盖
- 页面、CLI、Codex 也会一起被带偏
另外,bootstrap 计划和安装脚本都必须兼容多种部署目录布局:
${ROOT_DIR}/domainCheck/...${ROOT_DIR}/domain-api/...${ROOT_DIR}/deploy/...
原因很现实:
- 开发仓库里脚本位于
domain-api/deploy/... - 实际上线包可能已经把 API 内容平铺到
domainCheck/...
如果 bootstrap 计划只写死其中一种路径,就会在“控制面判定缺口正确,但落地脚本直接找不到 install script”这个位置翻车。
同样,这个 drill-down 也应该进入 doctor-export:
- 当
stack-diagnosis已经给出node_scene_log焦点 - 导出包自动追加
15_scene_node_log_<node_code>.txt manifest.json.summary.scene_log_reports也同步记录这些节点级现场日志附件
并且这里的焦点来源现在也应该统一成后端总检 contract:
diagnosis.issues[*].focus_refdiagnosis.next_step.focus_refdiagnosis.operator_decision.next_focus
只要其中任一层已经明确给出 node_scene_log,诊断包就应自动把该节点现场样本带上,而不是再由导出脚本自行重新推导“哪台机器值得看”。
这样海外 Codex、后台自动驾驶和人工交接看到的不是一句“建议去看某台节点”,而是一份已经带着关键现场样本的诊断包。
再往前一步,驾驶建议本身也不应只存在于前端页面,而应成为 ops overview contract 的一部分:
- 后端返回
driver_recommendations - 每条建议附带
action_code - 前端只做动作映射,不再重新计算优先级
继续往下,driver_recommendations 也不能只给“同一组 node_codes + 同一份 payload”。
因为真实场景里经常会出现:
- 主动作是
创建 Rollout - 次动作是
打开点状灰度模板 - 两个动作对应的目标节点、release_id、预填参数并不相同
所以 contract 还要支持:
primary_node_codessecondary_node_codesprimary_action_payloadsecondary_action_payload
这样海外控制面、Codex 驾驶员、CLI 推荐执行器才能真正做到“同一张建议卡,主次动作各走各的参数”,而不是被迫回退成前端手写分支。
这样未来换成 Codex 自动驾驶或脚本编排时,也能直接复用同一套推荐逻辑。
并且这套 contract 不该只停在“给建议”,而要继续补到“统一执行入口”:
POST /api/v1/ops/driver-actions/previewPOST /api/v1/ops/driver-actions/execute- 能后端化的动作先统一走这里
- 仍需人工选择、弹窗补参数的动作,再回退到前端交互
其中 preview 这层尤其关键,因为它负责把:
- execution chain
- target api / method
- 主次动作 request payload
- runbook resolve 结果
统一冻结成后端 contract。
这样页面、CLI 和海外 Codex 驾驶员都不需要自己重新拼 request preview,也不会再出现“页面自己猜 sequence -> action 映射”的口径漂移。
现在这条链路继续往下已经开始接入:
GET /api/v1/ops/playbooksGET /api/v1/ops/playbook-runsGET /api/v1/ops/playbook-runs/{run_code}GET /api/v1/ops/playbook-runs/{run_code}/eventsGET /api/v1/ops/activity-streamPOST /api/v1/ops/playbook-runs/{run_code}/rerunPOST /api/v1/ops/playbook-runs/{run_code}/cancelPOST /api/v1/ops/playbooks/previewPOST /api/v1/ops/playbooks/execute
也就是说,驾驶动作的后端执行入口不再只是“直接发一个动作模板”,而是可以把多步标准动作整体展开。
这样整条链路才是:
- 后端推荐
- 后端标准动作执行
- 前端只做展示和少量交互兜底
再进一步,控制面还要具备“巡检结果收口视图”:
- 一个节点最近的
health.snapshot - 一个节点最近的
logs.collect - 一个节点最近的
diagnostics.collect
要能在页面里按节点重新聚合,而不是让操作者自己翻几十条 ops job 去拼结果。
这一步很关键,因为它决定了:
- Codex 驾驶员读取的是“节点收口状态”
- 而不是一堆离散任务记录
现在这层也不该继续停留在前端临时聚合,而应该进入正式后端协议:
GET /api/v1/ops/overview返回inspectionGET /api/v1/ops/inspection-overview返回独立巡检收口视图- 页面只负责展示,收口排序、异常优先级、Worker 日志口径都以后端为准
并且这层 contract 不能只停留在“返回一句总结”,还要正式结构化:
problem_kindproblem_labelproblem_levelrecommended_action_codeui_intent
这样页面筛选、Codex 自动驾驶、后续策略编排才不需要再从自然语言里反推“这是失败、执行中还是巡检缺口”。
同理,动作模板 contract 也不能继续停留在“只有 text / number / select”:
booleantextareatext_list
这三类字段是运维动作真正高频的表达方式:
boolean用来表达是否自动审批、是否自动回滚、是否切 currenttextarea用来承载 artifact URL、长文本备注、临时参数块text_list用来承载服务列表、健康检查 URL 列表、节点列表
否则发布、回滚、批量诊断这些动作永远只能靠前端写死表单,无法真正回收到统一模板层。
尤其 deploy.release 这类动作,应该正式进入模板目录,而不是只存在于 rollout 内部:
deploy.release.controldeploy.release.workerdeploy.release.custom
这样单节点灰度验证、点状修复、正式 rollout 之前的预演,才能统一落到同一个 ops job 体系里。
模板动作再往前还要补一层正式能力:
action template previewbatch policy preview
也就是在真正创建 ops jobs 之前,控制面先基于:
- 目标节点集合
- 当前 execution mode
- 动作 payload
- 集群参与态 / 忙碌态 / control / effective worker 身份
给出统一预检结论:
blockedapproval_requiredwarningsrecommendations
这样发布、停 Worker、重启 API 这些高风险动作,就不会变成“点了按钮才知道会出事”,而是先看到:
- 哪些节点被阻断
- 哪些节点建议审批
- 哪些只是需要关注
这层能力既服务于页面按钮,也服务于后续的海外 Codex 驾驶员,因为两边都应该调用同一份 policy preview contract,而不是各自写一套风险判断。
在 playbook 这一层,还要继续补一条非常关键的 contract:
- 一次 playbook 执行 = 一次
playbook run - 每个 run 都要有自己的
run_code - 每个子任务都写入统一的 playbook metadata
- 控制面能够把这些子任务重新聚合成:
- run 总状态
- step 状态
- 目标节点集合
- 最近可追事件的任务入口
这一步的意义是把“多步动作”从前端临时串联,正式升级成可审计、可追溯、可回放的运维对象。
继续补齐 run 级动作之后,控制面就可以直接:
- 对 playbook run 做状态筛选
- 按分组收窄到 onboarding / diagnostics / scene
- 按 run_code、创建来源、目标节点做关键词检索
- 直接查看某轮 run 的 focus summary
- 直接看到 problem steps / active steps
也就是说,控制面针对 playbook run 的 contract 已经不再只是“能聚合”,而是进一步升级成“能定位焦点、能快速追问题”。
再往前一步,playbook run 还必须具备“整轮事件视角”:
- 不是只查某一条 job 的事件
- 而是能把同一
run_code下面所有 job 的事件重新聚合 - 并补齐:
step_keystep_titlejob_codejob_statustarget_node_code
这样海外控制面的详情抽屉才能真正像“远端驾驶舱”,因为它看到的是整轮执行过程,而不是零散子任务的碎片。
现在这层 contract 还要进一步冻结成“前端不再自行翻译”的正式字段:
playbook runstatus_labelsummarysummary_textsteps_totalsteps_successsteps_runningsteps_problemsteps_terminalfocus_ref
playbook run.steps[]status_labelsummarysummary_texttarget_node_codesfocus_ref
playbook run eventslevel_labeljob_status_labelsummarysummary_textevent_keyoccurred_atfocus_refsource_focus_ref
playbook run events.summaryreturned_totalevent_type_countsjob_status_countsfilters.limit
也就是说,从这一步开始:
- 前端不再自己把
running翻译成“收口中” - 前端不再自己拼“当前焦点摘要”
- 前端也不再把“播放焦点”和“远端执行焦点”混成一个对象
这里还要再补一条非常重要的消费规则:
playbook run events.focus_ref- 始终用于定位到本轮 playbook run 的正式落点
playbook run events.source_focus_ref- 始终保留远端 Agent / ops job 原始焦点
- 允许携带:
job_idjob_codeevent_keystep_key- 其他执行侧专有定位字段
这样控制面详情抽屉、Codex 驾驶员和后续 CLI 就不会再遇到一个老问题:
- 页面需要跳到“这一轮 playbook 的哪个步骤”
- 但排障又需要知道“远端 Agent 具体卡在了哪条事件”
这两种焦点必须并存,而不能互相覆盖。
- 前端不再自己推断“应该跳到哪一轮 / 哪一步 / 哪条事件”
而是统一消费控制面已经产出的结构化别名字段。
同样,驾驶建议层也开始和 playbook run 正式打通:
- recommendation 不再只推荐“去看哪个节点”
- 而是可以直接推荐“先处理哪轮异常编排”
- 并携带:
run_codefocus_step_keyfocus_step_title
这样 recommendation -> playbook run detail -> playbook run events 才形成一条真正闭环的驾驶链路。
继续往前收一层,recommendation 也不该只看 playbook run,而要继续消费统一 activity stream:
- 如果最近存在失败 / 阻断 / 已取消的 standalone
ops job - 或存在
awaiting_approval、halted、completed_with_issues的 rollout - recommendation 应直接把这条 activity 提升成驾驶建议
- 并通过统一
ui_intent把前端直接送到:job_eventsrollout_jobs
这样 recommendation 才真正具备“先聚焦异常活动,再决定是否补动作”的现场驾驶能力。
再继续往下一层,控制面还需要统一的“最近活动入口”:
- 不只是 playbook run
- 也包括 standalone ops jobs
- 以及 rollout 推进记录
所以活动流 contract 也应该成为正式后端协议:
GET /api/v1/ops/activity-stream- 每条 activity 带:
kindactivity_keystatusstatus_labeloccurred_atsummarysummary_texttarget_node_codesui_intentfocus_ref
其中 ui_intent 不是让前端自己猜跳哪,而是由后端直接声明:
playbook_run_detailjob_eventsrollout_jobs
这样后端不仅能告诉前端“这里有一条异常活动”,还能直接告诉它“应该落到哪一个处理界面”。
这样海外控制面、后台按钮和 Codex 驾驶员才能共享一套“最近活动 -> 正确落点”的跳转 contract。
并且这层不要再停在“页面展示友好字段”,而要正式成为统一驾驶对象:
status_label- 直接给页面、CLI、Codex 看的人类可读状态
summary_text- 直接给驾驶建议和摘要面板消费的短摘要
target_node_codes- 不让调用方再从自然语言里猜目标节点
focus_ref- 让页面、CLI、Codex 都能用同一份定位对象跳到:
playbook runops jobrolloutexecution scene
- 让页面、CLI、Codex 都能用同一份定位对象跳到:
这样后续不管是海外 Codex 驾驶员、CLI 还是后台按钮,都能把 activity 当成正式 contract,而不是“页面 table 的一行数据”。
同样,activity-stream 里的 ops_job 项也要冻结一条优先级规则:
- 如果 job 本身已经带
result.summary_text- activity 的
summary_text优先使用它
- activity 的
- 如果 job result 里已经带
focus_ref- activity 的
focus_ref优先吸收它
- activity 的
- 只有在没有明确执行结果摘要时
- 才回退到“目标节点 / 发起人 / 执行方式”这类模板化描述
原因很简单:
- 模板摘要适合“任务刚创建”
- 结果摘要才适合“任务已经执行过,且远端已经给出结论”
否则页面上会长期出现这种低信噪比口径:
目标节点 xxx / 发起 web-ui / 方式 远端 Agent
但真正更有价值的应该是:
远端 Agent 已回传 120 行 Worker 日志,可直接继续分析
这一点对海外 Codex 驾驶员尤其关键,因为它读取 activity-feed 时,优先需要的是“执行结论”,而不是“模板元信息”。
这条规则继续往下也要落到 Codex brief:
codex-brief.entries- 仍然是可执行主线
codex-brief.activity_focus- 则是可观察焦点
codex-brief.scene_log_observation- 则是节点级现场日志观察摘要
- 如果当前没有动作主线,但已有观察焦点
codex-brief.focus允许回退到首条activity_focus- 但推荐行为只能是
open_ui - 不允许伪装成
auto_execute
- 如果当前没有动作主线、也没有合适的 activity focus,但现场日志已经识别出明确目标节点
codex-brief.focus允许继续回退到scene_log_observation.focus_ref- 这样海外 Codex 会直接落到
node_scene_log
这样海外 Codex 在“现场正在变化,但暂时没有标准动作要打”的时候,仍然能稳定给出:
- 先盯哪一条
- 回到哪个正式落点
- 为什么现在是观察优先而不是执行优先
接下来 driver-feed / runbook 这一层也必须正式冻结:
driver_feed.entries[*]focus_refprimary_focus_refsecondary_focus_ref
runbook_sequences[*]focus_refprimary_focus_refsecondary_focus_ref
也就是说:
- driver-feed 不再只返回“动作码 + 按钮文案”
- runbook 也不再只返回“主次动作”
- 而是必须同时告诉页面、CLI、Codex:
- 这条主线整体应该聚焦哪里
- 主动作对应哪个正式落点
- 次动作对应哪个正式落点
这样后端才能真正消灭“前端根据 action_code 猜页面落点”的分叉逻辑。
这一层现在还要继续补一条非常实用的分流规则:
driver-feed.entries- 仍然表示“驾驶动作主线”
driver-feed.activity_focus- 单独表示“观察与跟踪焦点”
- 不再把观察项硬塞成一条可执行建议
driver-feed.scene_log_observation- 单独表示“当前远端日志回传是否已经形成可下钻的节点级现场日志”
也就是说以后海外控制面和 Codex 同时会拿到两类对象:
- 一类是可以继续 preview / resolve / execute 的动作主线
- 一类是当前值得优先盯住的现场对象
- 例如:
- 正在跑的
ops job - 未收口的
playbook run - 当前
execution scene - 远端
log sync
- 正在跑的
- 例如:
这样不会再出现一个老问题:
- 页面把“值得先看”误渲染成“可以直接执行”
- Codex 又把“先看现场”误读成“可以自动发动作”
同样的冻结规则还要继续往下压到驾驶执行链:
driver-actions/previewfocus_refprimary_focus_refsecondary_focus_refprimary.focus_refsecondary.focus_ref
driver-actions/resolvepreview_request.focus_refpreview_request.primary_focus_refpreview_request.secondary_focus_refgate.focus_ref- 顶层
focus_ref
codex-actions/resolveselected_entry.focus_refpreview_request.focus_refgate.focus_ref- 顶层
focus_ref
只有这样页面、CLI、海外 Codex 驾驶员在 preview / resolve / execute 三段链路里,才不会重新猜“当前应该回看哪个 release、哪个 rollout、哪个 execution scene”。这也是后续 NodeAgent、ReleaseHub、OpsCenter 可以共享同一份驾驶协议的关键。
进一步说,activity-stream 不只是最近事件列表,还应该是正式可筛选的观察面:
kindstatusquery
这些过滤条件必须后端支持,而不是只在前端本地过滤。这样未来:
- 海外 Codex 驾驶员
- CLI 检查脚本
- 后台页面
三者都可以复用同一套活动流收口逻辑。
继续升级之后,driver action 的协议也不该停在“一个 action_code”:
- 应返回:
handledmodeui_intent
- 其中
ui_intent用来表达:- 应打开哪个详情页
- 应聚焦哪一轮 playbook run
- 应进入哪个发布入口
- 应打开哪个节点接管入口
这一步的价值很大,因为它意味着:
-
recommendation 的解释权回到后端
-
前端变成“执行/跳转承载层”
-
后续如果换成海外 Codex 驾驶员,仍然可以直接复用同一份 intent contract
-
对某一轮编排做统一重跑
-
对某一轮编排做统一取消
-
不需要再由前端把同一批子任务逐条循环处理
这正是“海外控制面统一驾驶,大陆节点被托管执行”的关键基础。
同理,execution scene contract 也不该只是“参与节点数组 + 待命节点数组”,而要继续收成更明确的运行口径:
dispatch_active_nodesrecent_only_nodesstandby_nodesload_syncing_nodes
以及日志回传本身的正式状态:
disabledwaiting_samplepartial_coveragehealthyfull_capture
并且要直接返回:
- 覆盖了多少参与节点
- 哪些参与节点还没回传样本
- 当前推荐动作文案
这样页面、Codex 驾驶员和后续自动化脚本都能共享同一套“现场观察 -> 判断缺口 -> 执行动作”的协议,而不是各自再做一次前端猜测。
这样未来新增:
- Codex 自动巡检
- 后台按钮巡检
- 节点接管后的首轮验收
- 发布后的批次验收
都能直接复用同一个 inspection contract。
在收口视图之上,还应该继续有“异常节点优先队列”:
- 先处理失败 / 阻断 / 取消
- 再处理仍在执行中的节点
- 再处理尚未形成完整巡检记录的节点
另外标准巡检里的 logs.collect 应明确限定为 Worker 日志收口,不应把 Node Agent 的辅助排障日志混成同一类结果。
当前第一版 agent 执行器已经覆盖:
service.startservice.stopservice.restartservice.statushealth.snapshotlogs.collectdiagnostics.collectdeploy.release
后续继续增强时,只需要在这条主链路上加能力,不需要重换架构。
三、Agent 标准执行流程
1. 签发 token
控制面给节点签发一枚 agent token。
2. 节点注册
节点启动后调用:
register
上报:
node_coderegionroleagent_versioncapabilitieslabelshostnameip
3. 节点心跳
节点周期调用:
heartbeat
控制面更新:
last_seen_at- agent 元数据
- 节点在线状态依据
4. 节点拉任务
节点调用:
pull
控制面返回分配给该节点的 ops job。
5. 节点开工
节点拿到 job 后先调用:
jobs/{id}/start
这一步不是可有可无,而是为了把“任务已派发”和“任务已真实开始执行”区分开。
6. 节点完工
执行结束后调用:
jobs/{id}/complete
上报:
success / failed / partially_succeededstdoutstderrresult json
7. 节点事件流
执行过程中可持续调用:
jobs/{id}/events
把中间事件推回控制面。
四、为什么要有 Release Hub
因为正式环境不应该继续依赖:
- 节点自己
git pull - 工作区状态不确定
- root/www 用户混跑
正式版必须把“版本”变成平台中的一级对象。
也就是:
先有 Release,再有 Deploy
五、当前已经落下的 Release Hub 骨架
当前后端已经补出:
POST /api/v1/ops/releasesGET /api/v1/ops/releasesGET /api/v1/ops/releases/latestGET /api/v1/ops/releases/{id}POST /api/v1/ops/releases/{id}/activatePOST /api/v1/ops/releases/{id}/rolloutsGET /api/v1/ops/rollouts/{id}GET /api/v1/ops/rollouts/{id}/jobsPOST /api/v1/ops/rollouts/{id}/advance
并落了两类核心对象:
ops_releasesops_release_rollouts
这意味着后面发布系统可以正式围绕:
- 版本号
- 渠道
- commit sha
- artifact url
- checksum
- 激活版本
来运作。
但是 Release Hub 还不能只回答“有没有版本”,还要回答:
现在这版到底能不能发
因此控制面里还需要一个正式门禁对象:
release_hub.default_rollout_gate
它至少要返回:
- 默认 Release
- 默认 Rollout 目标节点
- 门禁状态:
missing_releaserelease_not_readyartifact_missingno_targetsblockedattentionready
- 阻断原因
- 告警原因
- 推荐动作
remote_agent_ready_nodes / nodes_totalinspection_healthy_nodes / nodes_total
这样首页驾驶建议、Release Hub 顶部摘要、Codex 自动驾驶、真正发 Rollout 前的 preview,才能统一读同一份门禁判断。
六、现在这版 rollout 已经不只是“建记录”
当前 rollout 已经补上三类关键能力:
- 分批推进
- rollout 与 ops job 关联
- 执行结果自动反推 rollout 状态
也就是说,控制面不再是一次性给所有节点平铺发任务,而是可以表达:
- 第一批先发
1台 canary - 健康后再发后续批次
- 某批失败则暂停
- 人工确认后继续下一批
当前 rollout 状态会根据 job 结果自动进入:
plannedrunningawaiting_approvalready_for_next_batchhaltedcompletedcompleted_with_issues
这已经非常接近正式发布系统的骨架,而不是临时脚本。
七、终局版发布链路
推荐的正式发布链路:
- 海外控制面构建 release
- 写入
ops_releases - 标记某个 channel 的 active release
- 后台点击“部署 release”
- 生成
ops job - node-agent 拉取任务
- 下载 artifact
- 校验 checksum
- 切换版本
- 重启服务
- 健康检查
- 成功或回滚
当前后端也已经补出:
- Release 对象
- Release 激活
- Rollout 对象
release -> rollout -> deploy jobs分批生成- rollout 推进接口
- 节点执行结果回写后自动刷新 rollout 总结
这意味着后续真正接发布包仓库时,只需要把 artifact 构建和分发接入,不需要再重新设计控制面对象模型。
八、Node Agent 侧的发布保险丝
当前 deploy.release 执行链路已经补上:
- 下载 artifact
- checksum 校验
- 解压到
releases/<version> - 切换
current软链 - 重启指定服务
- 执行健康检查
- 健康失败时回滚到旧版本
健康检查目前支持:
systemctl is-active- HTTP URL 探测
并且 agent 会把关键事件回推控制面,例如:
deploy_download_starteddeploy_checksum_verifieddeploy_current_switcheddeploy_health_faileddeploy_rollback_completed
这套事件流很重要,因为后面后台和 Codex 都是靠这条链路看懂“节点到底做到了哪一步”。
所以事件 contract 也要继续冻结,而不能只返回一行原始 message:
- 每条 job event 至少应带:
event_keyevent_typelevellevel_labelsummaryoccurred_atui_intent
- 任务事件接口本身还要有
summary:returned_totallevel_countsevent_type_countsnode_countslatest_at
这样海外控制面打开“任务事件流”时,先看到的是结构化执行回放,而不是先读一大串原始日志文本。
九、为什么这套设计后续最省心
因为它把三件事情分清楚了:
release是版本对象ops job是动作对象node agent是执行对象
后面不管节点从 3 台增加到 30 台,还是 Codex 接管更多动作,这三层都不用推翻。
十、接管不是终点,要有“验收后直接进入正式运维”的桥
实际落地里最容易断掉的一环,不是 token,也不是 heartbeat,而是:
节点明明已经接进来了,但接下来该做什么,操作员还得自己切页面、自己填 node_code、自己判断先巡检还是先发布。
这会把接管成功后的效率重新打回人工模式。
所以控制面里要把 Node Agent 接管链路拆成三段:
1. 接入观察
用于回答:
- token 是否有效
- node agent 是否已经 register
- heartbeat 是否稳定
- 当前是
未纳管 / 待接入 / 已接管 / 心跳过期
2. 标准验收
接管完成后,不应该直接默认“节点已经可用”,而是先跑一轮固定验收动作:
health.snapshotservice.status(domaincheck-node-agent)logs.collect(domaincheck-node-agent)service.status(domaincheck-worker)logs.collect(domaincheck-worker)
这轮验收的意义不是“多做几步”,而是把最常见的接管隐患一次性暴露出来:
- 服务虽然启动了,但 register 没成功
- heartbeat 有,但 pull / complete 异常
- worker 在线,但 Redis / DB / proxy 配置不对
- systemd 看似 active,但日志里已经反复报错
3. 正式运维入口
如果标准验收通过,控制面不能只显示一个“已通过”标签,而要直接给出两类后续入口:
进入巡检模式默认锁定当前节点,直接预填diagnostics.collect进入发布模式默认锁定当前节点,直接预填目标 release 和单节点 rollout
这样节点接管就形成了完整闭环:
看见节点 -> 纳管 -> 签发接入方案 -> 接入观察 -> 标准验收 -> 巡检 / 发布
而不是“接进来之后又回到人工记忆和手填”。
当前前端已经按这个思路落了一版:
- 接管弹窗里增加“接管验收结论”
- 可一键跑标准验收
- 验收通过后,直接出现:
进入巡检模式进入发布模式
- 节点会自动锁定为当前接管节点
- 发布会默认带上当前选中的 release,没有 release 时直接提示先创建
并且 OpsCenter 现在不再只是“有按钮”,而是开始把 contract 直接展示到关键弹窗里:
生成 Node Agent 接入方案- 明确显示当前使用
ops_agent_protocol - 展示主入口、schema 文档、发现入口、执行链路
- 明确显示当前使用
创建 Release- 明确显示当前使用
release_hub_contract - 展示 release 主入口、launchpad、schema 文档、发布链路
- 明确显示当前使用
创建 Rollout- 明确显示当前使用
release_hub_contract - 展示 rollout 主入口、launchpad、execution_mode、执行链路
- 明确显示当前使用
驾驶建议 / 驾驶主线 / 自动驾驶判断- 可直接打开“驾驶动作契约预览”
- 展示
ops_driver_contract、目标 API、执行链路、request preview overview / driver-feed已下沉到后端driver-actions/resolve/driver-actions/execute-resolveddriver-actions/preview保留为基础契约预览与兜底入口codex-focus-preview/codex-focus-run进一步下沉到后端codex-actions/resolve/codex-actions/execute- 把“这次点击到底会调用什么”从黑盒按钮变成可审阅对象
命令行侧也开始走同一条 contract 预览链:
bash domain-api/deploy/multi-region/drive_ops_center.sh contractsbash domain-api/deploy/multi-region/drive_ops_center.sh driver-feedbash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-previewbash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-runbash domain-api/deploy/multi-region/drive_ops_center.sh driver-preview ...bash domain-api/deploy/multi-region/drive_ops_center.sh driver-resolve ...bash domain-api/deploy/multi-region/drive_ops_center.sh driver-run ...bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-preview ...bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-run ...bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-previewbash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-run
这样海外主机上即使不打开页面,也能先看到统一 contract,再决定要不要执行。
这里还要再收一条非常重要的约束:
codex-focus-preview / codex-focus-run- 不能只认
codex-brief.entries - 当当前没有动作主线、但存在
codex-brief.activity_focus时- 应允许选中观察焦点
- 并统一收口为
focus_activity_item - 其 gate 必须落到
open_ui - 不能伪装成
auto_execute
- 不能只认
并且这条命令链现在已经补上了真正面向“单脑驾驶”的默认入口:
driver-feed- 看当前驾驶主线
driver-focus-preview- 默认消费
driver-feed.top_recommendation - 没传
entry_key时,不再要求人工先抄action_code - 如果显式传入的是
activity_focus.key- 应自动收口为
focus_activity_item - 只做“进入工作区”的观察焦点,不升级成自动执行
- 应自动收口为
- 默认消费
driver-focus-run- 先按同一条 focus 生成
resolve / execute-resolved请求 - 自动兼容:
- 普通
driver_action runbook_sequencesecondaryconfirm
- 普通
- 先按同一条 focus 生成
这样后续海外 Codex 驾驶员拿到的就不是“很多离散脚本”,而是一条稳定的动作主线:
- 看
driver-feed - 选
top_recommendation - 先
preview - 再按门禁
run
并且 codex-focus-run 已经开始真正落地“驾驶门禁”:
safe_auto- 直接执行
guarded_auto- 默认不执行,只输出审阅结果
- 只有显式
confirm才放行
resolve_first / open_ui / blocked- 一律不执行,只给出原因和目标入口
现在这层门禁已经不再由 CLI 本地解释,而是由后端统一返回:
selected_entrypreviewgateexecution_result
这样后续海外 Codex 驾驶员就不是“会调接口的脚本”,而是严格受同一份后端 contract 和门禁规则约束。
这样后续不管是人工点击、CLI 脚本调用,还是海外 Codex 驾驶员接管,都不会再出现“页面这样理解、后端那样理解、节点执行又是第三套”的口径漂移。
Release Hub 自己也要走同一套“标准动作对象”思路,而不是继续靠页面里临时拼表单。
当前已经开始往这条线上收:
发节点任务不再只是一个通用入口,而是正式区分:deploy.release.controldeploy.release.workerdeploy.release.custom
- 同时命令行侧的
check_release_hub.sh也会直接输出:- release 模板摘要
- 控制面批量预检
- Worker 批量预检
- 混合节点预检
这样页面按钮、海外 Codex 驾驶员、CLI 自检脚本三者看到的就是同一套发布口径,而不是三套不同的发布逻辑。
这一步非常关键,因为它把:
- Node Agent 接管
- Ops Job
- Release Hub
第一次真正接成了一条连续工作流。
十一、正式协议收口原则
为了让后续开发不再反复改口径,这套体系必须先明确 6 条协议原则:
1. 前后端与 Codex 共用同一份 contract
同一个动作、同一个节点、同一轮 rollout,必须由后端返回统一结构:
- 页面直接展示
- Codex 直接判断
- CLI 直接检查
不能让三边各自“再推导一次”。
2. 所有远控动作都要先变成对象
最终要统一成:
releaserolloutops jobplaybook runactivity
而不是:
- 页面点按钮直接跑 shell
- Codex 临时拼命令
- 节点本地脚本自己决定做什么
3. Node Agent 只执行已声明动作
Agent 不应该接受任意 shell 文本,而应该只接受标准动作:
service.startservice.stopservice.restartservice.statushealth.snapshotlogs.collectdiagnostics.collectdeploy.release
这样控制面才能做审计、门禁、批量预检和回滚。
4. 所有对象都必须能审计
至少都要带:
created_atupdated_atrequested_bytarget_node_code / target_nodesstatusresult
后面不管是人、后台还是 Codex 都要能回放过程。
5. 协议必须支持“逐步增强”
当前已经落下的是第一版骨架,所以协议要区分:
当前已实现字段后续建议补充字段
这样可以先稳定主链路,再逐步增强,而不是一开始就推翻。
6. API 返回必须统一包裹
当前所有接口都已经走:
{
"code": 0,
"message": "ok",
"data": {}
}
所以后面所有新增接口也应继续遵守:
code=0表示成功code=1表示业务失败- 具体错误语义继续放进
message和后续建议补的detail_code
十二、Node Agent HTTP 协议
1. 公共约束
请求头
Agent 相关接口统一使用:
Content-Type: application/jsonX-Domaincheck-Agent-Token: <token>
其中:
tokensbootstrap-plan
是控制面主动签发,不要求 agent token。
而以下接口要求带 token:
registerheartbeatpulljobs/{id}/startjobs/{id}/completejobs/{id}/events
公共节点载荷
当前代码里的 _base_payload() 已经收敛出最小节点信息:
node_coderegionroletitlehostnameipagent_versioncapabilitieslabelsmetadata.service_namesmetadata.delivery_queue
这意味着后续页面、Codex 和 agent 日志里看到的节点身份,应该都围绕这组字段统一。
其中 metadata.delivery_queue 当前已经是正式 contract,而不是预留字段。它至少会带:
statelabelreasonpending_countdead_letter_countlast_flush_atoldest_pending_atoldest_dead_letter_at
也就是说,heartbeat 不只是在汇报“Agent 还在线”,也在汇报:
当前本地是否有待补发回执、是否已经出现死信、最近一次补发冲刷是否成功
2. 签发 Agent Token
POST /api/v1/ops/agent/tokens
请求体
{
"node_code": "mainland-worker-02",
"issued_by": "web-ui",
"expires_in_hours": 72,
"metadata": {
"source": "ops-center"
}
}
当前返回字段
tokentoken_previewrecord_idnode_codeexpires_atcreated_at
设计要求
这一步只做“签发”,不做安装、不做纳管、不做启动。
也就是说:
- token 是凭证对象
- bootstrap plan 才是接入方案对象
3. 生成 Bootstrap Plan
POST /api/v1/ops/agent/bootstrap-plan
请求体
{
"node_code": "mainland-worker-02",
"node_region": "mainland",
"node_role": "worker",
"issued_by": "web-ui",
"expires_in_hours": 72,
"control_plane_base_url": "https://ops.example.com",
"root_dir": "/opt/domaincheck",
"metadata": {
"source": "ops-center"
}
}
当前返回字段
当前后端 build_node_agent_bootstrap_plan() 已返回:
- token 相关字段
control_plane_base_urlbootstrap_plan.node_codebootstrap_plan.node_regionbootstrap_plan.node_rolebootstrap_plan.root_dirbootstrap_plan.env_filebootstrap_plan.service_namebootstrap_plan.service_filebootstrap_plan.install_script_pathbootstrap_plan.env_contentbootstrap_plan.command_linesbootstrap_plan.command_blockbootstrap_plan.health_checksbootstrap_plan.health_check_blockbootstrap_plan.bootstrap_script_namebootstrap_plan.bootstrap_script_pathbootstrap_plan.bootstrap_script_contentbootstrap_plan.bootstrap_run_script_block
正式定位
bootstrap_plan 不只是“给一段命令”,它是:
控制面签发给目标节点的一份标准接管方案
后续:
- 页面按钮
- Codex 驾驶员
- CLI 辅助脚本
都应该复用这一个对象,不再自己拼接 env 或 shell。
4. Agent Register
POST /api/v1/ops/agent/register
请求体
{
"node_code": "mainland-worker-02",
"region": "mainland",
"role": "worker",
"title": "mainland-worker-02",
"hostname": "host-a",
"ip": "10.0.0.12",
"agent_version": "0.1.0",
"capabilities": [
"service.start",
"service.stop",
"service.restart",
"service.status",
"health.snapshot",
"logs.collect",
"diagnostics.collect",
"deploy.release"
],
"labels": {},
"metadata": {
"service_names": {
"api": "domaincheck-api",
"worker": "domaincheck-worker",
"sync_agent": "domaincheck-sync-agent",
"node_agent": "domaincheck-node-agent"
}
}
}
当前返回字段
node_codeexpires_atcapabilities
后端当前行为
当前 agent_register() 会:
- 校验 token 与
node_code - 调
_upsert_agent_runtime() - 更新
ops_managed_nodes
所以 register 的实际语义是:
让节点正式进入“已接管候选 / 已纳管”目录
5. Agent Heartbeat
POST /api/v1/ops/agent/heartbeat
请求体
当前与 register 共用同一份最小节点载荷。
当前返回字段
node_codeserver_timeexpires_at
协议语义
heartbeat 不是简单“在线 ping”,而是:
- 刷新 last seen
- 刷新节点身份与 service_names
- 维持控制面里 Node Agent 在线态
- 同步回执队列现场快照
当前已落地的队列快照字段
当前 heartbeat 已经会通过 metadata.delivery_queue 上报:
statehealthyretryingdead_letter
pending_countdead_letter_countlast_flush_atoldest_pending_atoldest_pending_request_idoldest_pending_kindoldest_dead_letter_atoldest_dead_letter_request_idoldest_dead_letter_kind
后续建议补充字段
后续建议 heartbeat 再扩:
active_jobsdisk_free_mbload_averagepython_version
其中 local_queue_depth 这一类信息,当前已经由 delivery_queue 快照承接,不再是空白能力。
6. Agent Pull
POST /api/v1/ops/agent/pull?limit=1
请求体
{
"node_code": "mainland-worker-02"
}
当前后端行为
当前 agent_pull_jobs() 只派发:
target_node_code = 当前节点status = queuedexecution_mode = remote-agent
并在派发时自动改成:
ops_jobs.status = dispatchingops_job_steps.status = dispatching
然后写入事件:
agent_dispatched
当前返回字段
{
"jobs": [
{
"id": 123,
"job_code": "ops-xxx",
"action": "health.snapshot",
"target_node_code": "mainland-worker-02",
"status": "dispatching",
"execution_mode": "remote-agent",
"payload": {},
"metadata": {},
"policy": {},
"steps": []
}
],
"count": 1
}
正式约束
后续就算扩到批量拉取,也应继续遵守:
- agent 只能拿到属于自己的 job
- agent 不负责选择 job
- 调度权始终在控制面
7. 标记任务开始
POST /api/v1/ops/agent/jobs/{job_id}/start
请求体
{
"node_code": "mainland-worker-02"
}
当前后端行为
当前 agent_mark_job_started() 会:
- 把
ops_jobs.status改成running - 把同 job 的 steps 改成
running - 写入
agent_started
设计要求
这一步必须保留,不能省略。
因为:
dispatching表示控制面已派发running表示节点已真实开始执行
这两者对排障非常重要。
8. 标记任务完成
POST /api/v1/ops/agent/jobs/{job_id}/complete
请求体
{
"node_code": "mainland-worker-02",
"client_request_id": "complete-ops-123-1",
"status": "success",
"stdout": "",
"stderr": "",
"result": {
"summary": "ok"
},
"error_message": ""
}
当前允许状态
successfailedpartially_succeeded
当前后端行为
agent_complete_job() 会:
- 校验 job 属于该节点
- 基于
client_request_id做完成回执幂等 - 更新
ops_jobs.status - 写入
result_json - 回写
error_message - 更新全部
ops_job_steps - 写入
agent_completed - 如果 job 属于 rollout,自动刷新 rollout 汇总
正式约束
节点只负责回写:
- 结构化结果
- stdout/stderr
- 失败原因
不负责自己修改 rollout 策略、release 状态或下个批次。
这些都必须仍由控制面决定。
当前已落地的幂等键
当前 complete 已支持:
client_request_id
控制面会把它落到:
ops_jobs.last_agent_complete_request_id
因此同一个完成回执即使因为网络抖动被重复补发,也不会再次制造副作用。
9. 上报中间事件
POST /api/v1/ops/agent/jobs/{job_id}/events
请求体
{
"node_code": "mainland-worker-02",
"client_event_id": "event-ops-123-checksum-1",
"step_id": 0,
"event_type": "deploy_checksum_verified",
"level": "info",
"message": "checksum ok",
"payload": {
"checksum": "sha256:..."
}
}
当前后端行为
agent_append_job_event() 会:
- 校验 token 和 node_code
- 基于
client_event_id做事件幂等 - 追加结构化事件
- 返回
event_id
正式要求
中间事件不能只传纯文本,至少还要带:
event_typelevelmessagepayload
因为后面:
- 页面活动流
- rollout 详情
- playbook run events
- Codex 驾驶员
都依赖结构化事件,而不是只靠日志文本猜。
当前已落地的事件去重约束
当前事件表已经有:
ops_job_events.client_event_iduq_ops_job_events_job_client_event
所以同一个事件即使被 Agent 重放,也应继续表现为:
- 控制面接收成功
- 不重复制造第二条事件
十三、Node Agent 状态机
1. Token 状态机
issueddisabledexpired
当前代码里 ops_node_tokens 已具备:
is_enabledexpires_atlast_used_at
所以 token 生命周期应该正式解释成:
issued + enabled + 未过期:可用issued + disabled:不可用issued + 已过期:不可用
2. 节点接管状态机
页面与控制面应该统一理解为:
未纳管待接入已接管仅运行态在线心跳过期token 过期 / 已禁用
建议正式映射为:
discoveredpending_bootstrapagent_onlineruntime_onlystaletoken_invalid
前端显示名可以中文化,但后端内部最好固定英文状态码。
3. Job 状态机
当前主链路已经形成:
queueddispatchingrunningsuccessfailedpartially_succeeded
正式语义应固定为:
queued:已创建,尚未派发dispatching:控制面已派发给节点,但节点尚未确认开工running:节点已开始执行success:完成且成功failed:完成且失败partially_succeeded:部分成功,需要人工关注
这套状态机后面不能再随意改名,否则活动流、巡检收口、rollout 汇总都会被打散。
十四、ops job 的 Agent 执行载荷标准
这部分后续建议直接以 ops_job_contract.md 为任务对象母本,再由 ops_agent_protocol.md 继续冻结 Agent 视角下的拉取、开工、完工与事件回执协议。
1. Agent 可见 job 最小字段
Agent 从 pull 接口拿到 job 时,最少应该依赖这些字段:
idjob_codeactiontarget_node_codeexecution_modepayloadmetadatapolicy
其他字段如:
requested_byrisk_levelapproval_statusrollout_id
虽然不是最小执行必需,但建议一起保留,便于未来 agent 诊断与本地审计。
2. 结构化动作必须优先
当前 agent 已按动作名分发:
supports_structured_action(action)execute_structured_action(...)execute_release_action(...)
这条设计必须坚持:
- 先结构化动作
- 再结构化 payload
- 最后才在执行器内部落成本地命令
不能反过来回到“控制面下发整段 shell”。
3. deploy.release 的 payload 基线
当前 release 执行器已经支持的正式意图至少包括:
- artifact 下载
- checksum 校验
- 解压到
releases/<version> - 切换
current - 重启服务
- 健康检查
- 失败回滚
因此 deploy.release 的 payload 应长期固定围绕:
release_versionartifact_urlchecksumrestart_serviceshealth_check_urlshealth_check_timeout_secondshealth_check_retrieshealth_check_interval_secondsrollback_on_failureswitch_current
这套字段已经足够支撑正式 release 部署。
4. logs.collect 的口径
标准巡检里的 logs.collect 应优先解释为:
收集目标服务日志,而不是执行任意日志命令
正式建议字段:
service_namelinesinclude_agent_logssince_seconds
并且默认仍要优先收口:
domaincheck-workerdomaincheck-node-agent
而不是允许页面任意填系统命令。
十五、Release Hub 正式契约
1. Release 对象
当前 ops_releases 已具备:
release_versionchannelcommit_shastatusartifact_urlchecksumnotesmetadatacreated_bycreated_atactivated_at
现在正式 contract 还需要进一步固定这些别名字段:
status_labelsummarysummary_textfocus_ref
正式上应把 Release 理解为:
一份不可变版本记录
也就是说:
- release 创建后,不应该允许直接把它重新指向另一份 artifact
- 同一
release_version只对应一份发布物
2. Rollout 对象
当前 ops_release_rollouts 已具备:
release_idrollout_codetarget_selectortarget_nodespolicystatusbatch_cursorbatches_totaljobs_totaljobs_createdresult_summary
而 rollout 这一层也不该再只返回“原始推进计数”,还应稳定返回:
status_labeltarget_node_codessummarysummary_textfocus_ref
所以 rollout 的正式定义应为:
某个 release 在某批目标节点上的一次分批推进计划
3. default_rollout_gate
这层是 Release Hub 最关键的后端 contract,必须作为正式对象保留。
至少应返回:
statusstatus_labelstatus_typesummarysummary_textreleaseexecution_modeexecution_mode_labeltarget_node_codesdefault_target_node_codesblocking_reasonswarning_reasonsrecommendationsoperational_readiness.summaryoperational_readiness.rowsfocus_ref
推荐状态码
missing_releaserelease_not_readyartifact_missingno_targetsblockedattentionready
这组状态码后面必须同时服务:
- 首页驾驶建议
- Release 页面门禁卡
- Rollout 预检
- Codex 自动驾驶
4. release_launchpad
除了默认门禁外,还应保留更偏“驾驶舱”的发布聚合对象:
latest_packagelatest_releaseworker_rollout_previewcontrol_rollout_previewlaunchpad_status
其中 launchpad_status 至少应回答:
- 当前应该先补什么
- 推荐动作码是什么
- 推荐执行方式是什么
- 是先看 Worker 预案还是先看 Control 预案
- 当前是否还存在接入缺口
- 首个缺口节点是谁
- 这个缺口是
bootstrap_pending- 还是
acceptance_ready
并且这层也应该直接给出:
summary_textfocus_refrecommended_target_node_coderecommended_recovery_labelrecommended_recovery_summaryonboarding_bootstrap_pending_nodesonboarding_acceptance_ready_nodes
其中 focus_ref 不该只是“回到 release hub”这么模糊,而应该允许继续携带:
sectionmode
这样页面、CLI、Codex 都不需要自己再推断“该跳回 release hub 的哪个区域”。
这里再固定一个已经收口成代码字段的判断口径:
- 如果
launchpad_status.recommended_action_code已经落在bootstrap_runrun_acceptance
- 那么页面、CLI、Codex 不应再只显示笼统的“待补执行面”
- 而应直接把以下字段作为主提示:
recommended_target_node_coderecommended_recovery_labelrecommended_recovery_summaryonboarding_bootstrap_pending_nodesonboarding_acceptance_ready_nodes
原因是:
- 真正的阻断并不总是“发布包有问题”
- 很多时候是“发布包已经好了,但节点还没接管完”
- 如果 launchpad 只返回一个大而化之的
attention前端和 Codex 还得再各自遍历 gap rows 去猜 - 这会重新制造多套判断逻辑
所以现在要把 launchpad 的缺口口径固定成:
launchpad_status直接给驾驶层结论gap_rows继续给详情层做下钻
而不是反过来让驾驶层去解析详情层。
当前仓库里这套骨架已经存在,所以后续不要再新造第四套发布摘要模型。
十六、Rollout 状态机
当前 rollout 状态已经接近正式版,建议固定为:
plannedrunningawaiting_approvalready_for_next_batchhaltedcompletedcompleted_with_issues
正式语义
planned:已创建,尚未开始发批次running:当前批次已发出,正在等待 job 结果awaiting_approval:下一步推进需要人工确认ready_for_next_batch:当前批次通过,可进入后续批次halted:由于失败、阻断或人工暂停而停止completed:全部批次完成且整体健康completed_with_issues:全部批次结束,但存在失败或告警
后续不建议再引入新的近义状态名,否则页面和 Codex 会反复适配。
十七、错误码、幂等与重试规则
1. detail_code 已经进入 Agent / Ops 主链路
现在统一响应体已经有:
codemessagedata
而且 detail_code 已经开始进入顶层或 data 中,成为控制面和 Agent 都能消费的结构化错误语义。
这层现在的意义不再是“未来建议”,而是:
不再靠中文 message 猜失败原因,而是让页面、Codex 和 Node Agent 共用同一份错误码
当前主链路里的错误返回,已经会继续补:
detail_code
例如:
agent_token_invalidagent_token_expiredagent_token_node_mismatchops_job_not_owned_by_agentops_job_invalid_status_transitionrelease_checksum_mismatchrelease_health_check_failed
这样页面和 Codex 就不用再从中文 message 里反推错误类型。
2. 幂等 contract
当前已经正式落地两条关键幂等线:
complete- 使用
client_request_id - 回写到
ops_jobs.last_agent_complete_request_id
- 使用
events- 使用
client_event_id - 通过
uq_ops_job_events_job_client_event去重
- 使用
这意味着 Agent 在遇到控制面瞬断、网络抖动或重试补发时,已经具备:
- 回执不重入
- 事件不重复落库
后续仍建议继续保持幂等的动作有:
以下动作应该尽量幂等:
registerheartbeatbootstrap-planactivate releaserollout preview
也就是重复请求最多刷新状态,不应制造重复副作用。
3. Agent 重试与本地补发规则
建议正式约束,并且当前主链路已经落下一版:
register失败:指数退避重试heartbeat失败:短退避重试,不立即退出pull失败:进入轮询退避start/complete/events失败:本地保留待补发队列
其中现在已经正式落地的是:
completeevents
这两类回执在失败时会进入 Agent 本地待补发队列,而不是直接丢失。
4. 当前已经落地的本地待补发 / 死信队列
当前 domain-api/app/node_agent.py 已经具备:
- 本地
pending队列 - 本地
dead-letter队列 - 定时
_flush_delivery_queue() - 对永久性
detail_code的死信分流
也就是说,Agent 当前不是“请求失败就算了”,而是已经形成:
- 优先即时发送
- 失败则本地排队
- 下个 tick 自动重放
- 如果命中永久性错误,则进入死信
这层能力当前主要覆盖:
jobs/{id}/completejobs/{id}/events
5. 控制面现在如何看见队列问题
这条链路现在也已经不只存在于 Agent 本地。
当前控制面会把 heartbeat 里的 metadata.delivery_queue 收口成:
- 节点行字段
delivery_queue_statedelivery_queue_labeldelivery_queue_reasondelivery_queue_pending_countdelivery_queue_dead_letter_count
- 节点汇总字段
queue_retrying_nodesqueue_dead_letter_nodesqueue_pending_recordsqueue_dead_letter_records
并且驾驶建议已经开始优先提升这类问题:
- 如果存在死信节点,优先建议先处理 Agent 死信
- 如果存在待重试积压,优先建议先观察回执积压
所以这已经不是“隐藏在节点本地目录里的运维细节”,而是正式进入海外控制面的驾驶 contract。
十八、终局验收标准
当这套体系真正算“上线可用”,至少要满足以下 10 条:
1. 新增节点不再需要人工拼命令
控制面能够直接生成:
- token
- env
- bootstrap script
- health check block
2. 海外控制面能先看见节点,再决定是否接管
不是只有接入成功后才看见机器。
3. 接管完成后可直接跑标准验收
而不是再去手工 systemctl / journalctl。
4. 标准巡检统一走 ops job / playbook run
而不是页面每个按钮都各做一套逻辑。
5. 真正参与检测节点和在线未参与节点口径统一
页面、后端、Codex 必须读同一份 execution scene。
6. 远端日志回传状态正式结构化
至少统一成:
disabledwaiting_samplepartial_coveragehealthyfull_capture
7. 发布必须先有 Release,再有 Rollout
不能回退到节点直接 git pull。
8. Rollout 必须能分批、暂停、继续、回滚
而不是一次性把所有节点推平。
9. 页面按钮和 Codex 都只创建对象,不直接执行 shell
最终:
- 页面创建
ops job - Codex 创建
ops job/playbook run - agent 消费
ops job
10. 海外主机必须成为唯一运维大脑
大陆机器只负责:
- 执行
- 回传
- 承载业务
不再承担“人工登录后做决定”的角色。
十九、下一步最值得继续补的 6 件事
基于当前仓库状态,接下来最值得继续补的,不再是“把主链路补出来”,而是把已经成型的体系收成真正可长期运维的平台。
1. 给死信队列补正式操作面
当前已经有:
- 本地待补发
- 本地死信
- 控制面可见的队列快照
下一步最该补的是:
- 死信详情查看
- 死信重放
- 死信丢弃
- 死信按
detail_code聚类
否则“看见死信”之后,操作者还是得回到节点本地处理。
2. 把 action template preview / batch policy preview 正式后端化
在真正创建 ops jobs 之前,要让控制面先统一回答:
- 哪些节点被阻断
- 哪些节点需要审批
- 哪些节点只是 warning
- 当前批次策略是否合理
这样发布、重启、停 Worker 这类高风险动作,才能在创建对象前先收口风险。
3. 把接管验收、标准巡检、现场观察彻底收进 playbook run
现在 playbook run 骨架已经有了,而且其中一部分已经开始从“打开弹窗”进入“直接执行正式动作”。下一步要继续把:
onboarding.acceptanceinspection.standardscene.logs.keyscene.logs.full
都变成真正稳定的编排对象,而不是“部分走编排、部分还停留在前端直触发”。
当前已经落地的直接驾驶动作包括:
bootstrap_runrun_acceptancerun_standard_inspectionopen_diagnosticsrun_scene_logs_keyrun_scene_logs_full
这意味着纳管收口、标准巡检和现场观察都已经开始直接进入正式执行链,而不是先把操作者送去弹窗二次选择。
发布审阅链也应该遵守同样原则:
review_smart_rollout_previewreview_control_rolloutfix_rollout_blockers
这几类动作不应只返回“打开 launchpad 审阅页”的 UI intent,而要同时直接返回结构化 release_launchpad_review 结果,让自动驾驶、CLI 和后端任务能直接消费 launchpad 预案。
并且这里的自动化等级也要彻底锁死:
- 这三类动作属于
safe_auto - 它们的职责是“审阅”和“解释”
- 不是“创建发布对象”
所以默认返回应是:
- 结构化
release_launchpad_review - 可选
ui_intent = release_launchpad_review - 但不直接创建 rollout / publish 记录
同理,focus_release_hub 也不应继续只是一个纯导航意图:
- 页面侧仍可消费
ui_intent = focus_release_hub - 但后端同时还应返回
release_hub_preview至少包含:summarylaunchpad
这样海外 Codex、CLI、driver-feed 调用方即使不打开页面,也能继续拿这份摘要决定后续是:
- 继续审阅 launchpad
- 进入发布创建
- 还是先回补门禁缺口
release_package 也应遵守同样的“先给后端摘要,再保留 UI 入口”原则:
- 页面仍可通过
ui_intent = open_release_dialog进入打包区 - 但动作返回里还应包含
release_package_preview至少包括:availablepackage_namerelease_version_suggestionreason
这样 Node Agent、Release Hub、海外 Codex 驾驶员三边的判断都能先基于同一份“发布包现场状态”,而不是必须打开页面后才能知道有没有包可发。
同理:
open_release_dialog也不应只是纯导航 应同时返回release_dialog_previewopen_rollout_dialog也不应只是纯导航 应同时返回rollout_dialog_preview
这样就算暂时仍保留页面入口,自动驾驶层也已经可以先读:
- Release 当前摘要
- Launchpad 当前摘要
- 发布包是否存在
而不是必须依赖前端抽屉作为唯一信息入口。
继续往下一层,模板类动作和发布部署模板动作也应该统一走“预览优先”:
open_playbook_dialog应返回playbook_previewopen_action_template_dialog应返回template_previewopen_release_deploy_worker / open_release_deploy_control / open_release_deploy_custom应返回release_deploy_preview
其中 release_deploy_preview 至少应包含:
templatesummarylaunchpadtarget_node_codesexecution_moderelease_id
这样自动驾驶层拿到的就不是“去某个弹窗”,而是一份已经够继续判断的执行预案。
再往前,焦点类动作也不应继续只是“跳到某个详情页”:
focus_playbook_run应返回playbook_run_focusopen_playbook_run_latest_events应返回playbook_run_events_previewfocus_latest_job_events应返回job_events_previewfocus_activity_item应返回activity_focus_preview
这样 Codex、CLI、OpsCenter 三边在进入具体页面之前,就已经能先读到:
- 当前编排主状态
- 最近事件流
- 当前 job 回执
- 当前 activity 焦点对象
后续“跳到页面”就只是补充查看,而不是唯一信息入口。
这里还要把动作分级口径正式固定下来:
- 只要动作已经满足:
- 后端可直接执行
- 不修改现场状态
- 主要返回
preview / focus / summary / events
- 那么它就不应继续归在
ui_only - 而应统一归到
safe_auto
当前已经应按这个口径归类的动作包括:
handover_first_gapview_first_gapopen_playbook_dialogopen_action_template_dialogfocus_playbook_runfocus_activity_itemfocus_latest_job_eventsopen_playbook_run_latest_eventsopen_release_dialogopen_rollout_dialogopen_release_deploy_controlopen_release_deploy_workeropen_release_deploy_customfocus_release_hubrelease_package
也就是说,页面仍然可以消费这些动作的 ui_intent,但 Codex / CLI / 自动驾驶网关已经可以把它们当成:
- “可直接执行的只读观察动作”
而不是:
- “必须先打开某个页面才能继续”
而真正进入发布创建层的动作,例如:
publish_latest_workercreate_smart_release_rollout_workercreate_smart_release_rollout_controlcreate_release_rollout_workercreate_release_rollout_control
则必须继续保持:
guarded_autoconfirm_then_execute- 正式写入发布与审计链
4. 把 Release Artifact 管道做成正式不可变发布链
现在 Release / Rollout 对象已经成型,下一步最关键的是补齐:
- 构建产物打包
- checksum / manifest
- 上传与留存
- channel 激活
- 回滚版本选择
也就是把“版本对象存在”继续推进到“版本供应链完整”。
5. 把 SSH Rescue Executor 收成首发接管与紧急救援闭环
终局不是回到 SSH 主运维,但 SSH 仍应有正式位置:
- 首次 bootstrap
- Node Agent 尚未接入时的应急接管
- Agent 故障时的救援执行
这一层应继续做成正式 executor,而不是保留为人工口令。
6. 把 Node Agent / ReleaseHub / OpsCenter contract 固化为 schema / OpenAPI
现在最怕的已经不是“没有能力”,而是:
页面、后端、Codex、CLI 在继续迭代时发生字段漂移
所以后续上线前,建议正式抽出:
schemas/ops_agent_protocol.mdschemas/release_hub_contract.mdschemas/ops_driver_contract.mdschemas/ops_playbook_contract.md
并且不只要有 schema 文件,还要让控制面直接暴露统一索引:
GET /api/v1/ops/contractsGET /api/v1/ops/contracts/{contract_key}
前者负责列出整张 contract map,后者负责按 key 钻取单份协议和相关 contract。
控制面前端与海外 CLI 也要统一走这两层入口:
- 页面默认只拉 registry,避免首屏过重
- 当用户点击某个 contract 时,再按 key 拉 detail
- detail 返回里必须带
related_contract_keys/related_contracts - 这样排查一条链路时,可以从
ops_stack_diagnosis_contract直接跳到ops_driver_contract、release_hub_contract、ops_agent_protocol
或者直接写入后端 schema / OpenAPI 注释,让 contract 成为真正受版本控制的对象。
二十、海外 Codex 驾驶员的正式闭环
如果要把“海外主机单脑控制面”真正做成长期稳定体系,海外 Codex 驾驶员不应该被设计成:
- 远端 shell 执行器
- 看日志后临时拼命令的脚本
- 依赖人工不断复制上下文的助手
它应该是严格跑在统一 contract 上的“驾驶层”。
1. 默认入口只看 3 类信号
海外 Codex 驾驶员默认只消费:
driver-feedcodex-briefactivity-stream
分别回答:
- 现在最该处理什么
- 这一步能不能自动执行
- 最近执行后现场发生了什么
也就是说,驾驶员不应该先去翻零散日志,再猜下一步,而是先看控制面已经结构化好的主线。
2. 默认执行顺序必须固定
标准顺序应固定成:
driver-feed- 取
top_recommendation
- 取
driver-focus-preview- 看 contract / gate / request preview
driver-focus-run- 只有后端 gate 允许时才执行
activity-stream- 回看这一步是否真正落地
- 必要时钻取:
playbook runops jobrelease / rollout
这样驾驶员并不是“直接开车”,而是:
- 先看路标
- 再看交通规则
- 然后再执行
3. 驾驶员永远不直接决定 shell
正式规则必须收死:
- 驾驶员不直接 SSH 到节点上拼 shell
- 驾驶员不自己判断某台机器该不该
git pull - 驾驶员不自己决定 systemd 命令如何组合
- 如果控制面已经把首个缺口节点收敛成
bootstrap_run / run_acceptance- 驾驶员也不能退回成泛化的
fix_managed_nodes - 而应优先围绕同一个节点继续做预览、执行和回执追踪
- 驾驶员也不能退回成泛化的
驾驶员只做三件事:
- 选择动作对象
- 请求后端解析门禁
- 创建正式执行对象
最终执行必须落到:
ops jobplaybook runrelease / rolloutnode agent task
这样才具备:
- 审计
- 回放
- 回滚
- 风险分级
4. 驾驶员必须把“看见问题”和“处理问题”拆开
终局方案里最容易犯的错,就是把:
- 看见节点离线
- 打开日志
- 重启服务
- 改配置
- 再观察
全部写成一段混杂的脚本。
正确方式应该拆成两层:
- 观察层
driver-feedactivity-streaminspectionrelease launchpad
- 执行层
driver-actions/execute-resolvedops jobplaybook runrollout
这样后面无论是:
- 人工值班
- 海外 Codex 自动驾驶
- 后台一键按钮
都能共用同一套观察口径与执行口径。
补充一条正式约束:
driver-feed- 顶层 payload 需要带
contract_navigation - 每条
entries - 每条
activity_focus都要带contract_navigation
- 顶层 payload 需要带
activity-stream- 顶层 payload 需要带
contract_navigation - 每条
items都要带contract_navigation
- 顶层 payload 需要带
这样海外单脑控制面里的卡片、时间线、CLI drilldown、Codex 驾驶员才能共享同一条“协议跳转链”,而不是 UI 有一套、CLI 有一套、排障时再临时翻文档。
5. 驾驶员需要一套明确的升级路径
真正上线时,建议把海外 Codex 驾驶员分成 4 档能力,而不是一步到位全自动:
L1 观察驾驶
- 只看
driver-feed / codex-brief / activity-stream - 只输出建议,不执行
L2 门禁驾驶
- 可以跑
driver-focus-preview - 可以对
safe_auto动作执行driver-focus-run - 对
guarded_auto只提示确认
这里的典型分界现在应明确到动作名,避免后续接入新驾驶员时再自己猜:
safe_autoreview_smart_rollout_previewreview_control_rolloutfix_rollout_blockersrun_standard_inspectionrun_scene_logs_keyrun_scene_logs_full
guarded_autopublish_latest_workercreate_smart_release_rollout_workercreate_smart_release_rollout_controlcreate_release_rollout_workercreate_release_rollout_control
L3 编排驾驶
- 可以创建:
playbook runops jobrelease / rollout
- 但仍不直连 shell
L4 救援驾驶
- 仅在 Node Agent 失效或接管初期
- 允许走受控
SSH Rescue Executor - 仍然必须把动作写回控制面审计链
这样自动化能力能逐档放开,而不是一开始就把所有权力交给一段脚本。
6. 海外主机最终应该成为唯一“决策写入口”
终局不是“海外主机能看到更多日志”,而是:
所有决策类动作,都只能从海外控制面写入。
这意味着:
- 大陆节点不再自己
git pull决定版本 - 大陆节点不再靠人工 SSH 决定是否重启
- 大陆节点只负责执行、回传、承载业务
而海外主机负责:
- 产物选择
- 风险判断
- 动作签发
- 回执汇总
- 运行态总览
这才是真正的“单脑控制面”。
7. 对你这个项目,最推荐的默认日常路径
如果按最省心、后续最少返工的标准,建议以后日常操作尽量收成下面这条路径:
- 海外主机打开 OpsCenter
- 看
driver-feed.top_recommendation - 先
driver-focus-preview - 再
driver-focus-run - 如果动作升级,进入:
inspectionplaybook runrelease hub
- 如果执行异常,看:
activity-stream- 死信队列
- 回执补发状态
- 只有在 Agent 不可用时,才进入 SSH Rescue
这样后续再新增 1 台大陆机器,不会再变成“重新写一套部署说明”,而只是:
- 新节点 bootstrap
- Node Agent 注册
- 控制面验收
- 进入正式运维
架构不会再被机器数量拖垮。
二十一、死信队列必须从“可见”升级到“可操作”
当前这套体系已经能看见:
- 哪台节点存在
dead_letter - 有多少条死信
- 最早死信时间是什么
这说明“现场可见性”已经有了。
但如果没有正式操作面,控制面还是会卡在最后一步:
- 看到死信
- 给出建议
- 最后仍然要人工 SSH 去处理
这就违背了“海外单脑控制面”的目标。
所以这层必须继续升级成正式对象:
- 单节点队列总览
- 队列记录列表
- 死信详情
- 单条重放
- 批量重放
- 丢弃
- 主动冲刷
其中第一阶段已落地:
GET /api/v1/ops/nodes/{node_code}/delivery-queueGET /api/v1/ops/nodes/{node_code}/delivery-queue/recordsPOST /api/v1/ops/nodes/{node_code}/delivery-queue/flushPOST /api/v1/ops/nodes/{node_code}/delivery-queue/replayPOST /api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/replayPOST /api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/discard
当前这组入口明确采用:
record_visibility=head_only
也就是控制面先稳定看到:
- 节点级队列状态
pending / dead_letter头部记录
而不是假装已经具备远端全量死信列表与直接操作能力。
也就是说现在已经进入“可操作的第一阶段”:
- 控制面能看节点级队列状态
- 控制面能看
pending / dead_letter头部记录 - 控制面能通过正式
ops job触发冲刷 / 重放 / 丢弃 - 但还没有把远端全量记录目录完整投影成控制面对象
并且这第一阶段现在已经不是“后端接口预留”,而是已经形成了三层统一操作面:
- OpsCenter 托管节点表可直接打开“回执队列治理”抽屉
- driver recommendation 遇到
dead_letter / retrying时,会优先落到标准动作模板 - 海外单入口 CLI 已支持直接查看 / 冲刷 / 重放 / 丢弃
也就是说当前这层已经具备明确的人机协作分工:
- 页面负责看现场、做单条处理、做危险动作确认
- driver / Codex 负责把“当前最该处理什么”压成建议
- CLI 负责在海外主机上做统一批处理和交接执行
具体到当前仓库,已经形成下面这些稳定入口:
1. 页面入口
- OpsCenter 托管节点表:
队列立即冲刷重放死信单条重放单条丢弃
- 丢弃动作强制要求填写原因
- 当前页面仍明确遵守:
record_visibility=head_only
也就是页面不会假装自己能枚举远端完整死信目录,而是只围绕“当前可见头部记录”和“节点级摘要”操作。
2. 驾驶建议入口
当前 driver_recommendations 已经升级成:
dead_letter- 主动作:直接执行
replay_delivery_queue - 后端落点:创建
delivery.queue.replay的标准ops job batch - 次动作:查看 Worker 日志
- 主动作:直接执行
retrying- 主动作:直接执行
flush_delivery_queue - 后端落点:创建
delivery.queue.flush的标准ops job batch - 次动作:查看 Worker 日志
- 主动作:直接执行
这一步很关键,因为它意味着控制面不再只是说“这里有死信,你自己想办法”,也不再只是把人送去模板弹窗,而是已经能直接走正式 ops job 链路下发队列治理动作。
3. 海外单入口 CLI
当前 drive_ops_center.sh 也已经补上了这组命令:
bash domain-api/deploy/multi-region/drive_ops_center.sh queue-status <node_code>bash domain-api/deploy/multi-region/drive_ops_center.sh queue-records <node_code> [state]bash domain-api/deploy/multi-region/drive_ops_center.sh queue-flush <node_code> [limit] [requested_by]bash domain-api/deploy/multi-region/drive_ops_center.sh queue-replay <node_code> [limit] [requested_by]bash domain-api/deploy/multi-region/drive_ops_center.sh queue-replay-record <node_code> <record_id> [requested_by]bash domain-api/deploy/multi-region/drive_ops_center.sh queue-discard-record <node_code> <record_id> <reason> [requested_by]bash domain-api/deploy/multi-region/drive_ops_center.sh activity-stream [key=value ...]bash domain-api/deploy/multi-region/drive_ops_center.sh activity-preview [activity_key] [requested_by]bash domain-api/deploy/multi-region/drive_ops_center.sh driver-feedbash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-previewbash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-runbash domain-api/deploy/multi-region/drive_ops_center.sh codex-briefbash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-previewbash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-run
建议把这组命令视为“海外集中治理 Node Agent 回执队列”的正式入口,而不是临时调试命令。
并且现在这组命令的输出口径也要冻结下来:
activity-stream / driver-feed / codex-brief- condensed summary 必须打印顶层
contract_navigation
- condensed summary 必须打印顶层
activity-preview / driver-focus-preview / codex-focus-preview / stack-next- condensed summary 必须打印
preview.contract_key / contract_version / contract_primary_endpoint / preview_endpoint / schema_doc_path
- condensed summary 必须打印
- 所有
selected_entry- 必须尽可能保留
contract_navigation
- 必须尽可能保留
这样海外 CLI、页面按钮、未来海外 Codex 驾驶员看到的就不是三套不同“解释文本”,而是一套可追踪到协议、接口和 schema 文档的正式输出。
同理,海外单脑控制面导出的交接包也不能只停留在:
stack summaryops planeparticipation
还应该把 contract-aware 驾驶面一起固化进去:
contracts registrydriver-feedactivity-streamcodex-briefstack-next previewdriver-focus previewactivity previewcodex-focus preview
并且 preview 类报告允许“当前没有焦点但仍导出文件”,文件尾部只记录 command_exit_code=1,这样交接人拿到包以后不会误解成“导出中断”,而能明确判断是“现场当时没有可预览入口”。
进一步讲,这个交接包还应该再固定一层机器摘要:
manifest.json
它不是替代原始报告,而是给海外 Codex / 后台自动驾驶 / 未来 OpsCenter UI 二次消费的摘要面。
正式口径应该固定成:
meta.json- 负责说明这包是谁、何时、针对哪个 profile / 哪组 API 生成的
manifest.json- 负责说明这包里每份报告是否存在、exit code 是多少、哪些 preview 当前可用、现场暴露了哪些 contract key
- 如果某个 required 报告已经解析出
http_status且不是2xx,也必须记入required_failures
*.txt- 保留完整原始现场输出,供人类继续钻取
并且 doctor-export 命令自己的 stdout 也应该回传一份缩略版 summary + decision,这样海外 Codex 不必先打开文件树,也能先做第一轮分流判断。
进一步往终局落地时,CLI 侧还应该固定一个更适合机器先消费的统一入口:
doctor-decision
正式定位应该是:
doctor-export- 负责生成完整交接包
doctor-decision- 负责读取
manifest.json - 如果没有现成
manifest.json,就临时生成一份 export - 然后只回传
summary + decision - 并且允许用
-明确关闭 overseas 拓扑探测,避免因为默认海外地址不可达把第一轮分流卡死
- 负责读取
这里的 summary 不能只做 surface 成败汇总,还要继续固定带出 launchpad 接管口径:
launchpad_recommended_target_node_codelaunchpad_recommended_recovery_labellaunchpad_recommended_recovery_summarylaunchpad_onboarding_bootstrap_pending_nodeslaunchpad_onboarding_acceptance_ready_nodes
同时,decision.evidence 也要镜像这组字段,确保海外 Codex / 自动驾驶器 / 人工值班
在只消费 doctor-decision 的前提下,就能直接判断:
- 当前最该先接哪台节点
- 当前是“还缺 bootstrap”还是“已经 ready,等待 acceptance”
- 是否需要继续下钻
release-launchpad / driver-feed / codex-brief
这样海外单脑控制面的“第一跳”就稳定了:
- 海外 Codex 先跑
doctor-decision - 后台自动驾驶按钮先跑
doctor-decision - 人工值班也先看
doctor-decision
并且从当前实现口径上,doctor-decision 不应再只消费旧的 next_step_action_code,而要优先消费:
diagnosis.operator_decisiondiagnosis.next_actionsdiagnosis.recommended_commands
这意味着如果总检已经明确判定“先看现场日志 / 先打通节点接管 / 先看 Release Hub”,那么 doctor-export 产出的 manifest.json 与 doctor-decision 最终给出的 recommended_commands 也必须同步落到同一条主决策,不允许再次退化成泛化建议。
只有当 decision.status 显示需要继续钻取时,才去打开整包 *.txt 和 manifest.json 深挖。
而且 doctor-export / doctor-decision 这一层本身也应该固定成“步骤级超时继续导出”:
- 某个 surface 卡住,不允许整包诊断悬死
- required report 失败,要进入
required_failures - required report 失败明细,要进入
required_failure_details - optional preview 不可用,要进入
optional_unavailable - optional preview 不可用明细,要进入
optional_unavailable_details - 海外单脑永远优先拿到一份可判断的
decision,而不是拿到一个还在转圈的进程
并且这些明细不能只停留在“文件名失败”:
reports[].failure.reason_codereports[].failure.labelreports[].failure.detail
至少要能稳定区分:
command_timeoutcommand_failedhttp_unreachablehttp_errorresult_unparsed
再往前一步,控制面 API 自身也应该暴露正式“运行指纹”:
/health/api/v1/runtime/status/api/v1/runtime/build-info
这些接口至少应该稳定给出:
build.sourcebuild.package_namebuild.generated_atbuild.commit_shabuild.commit_refbuild.manifest_pathbuild.route_surface.surface_completebuild.route_surface.missing_paths
这样以后看到 ops/contracts 返回 404 时,就不用再靠猜:
- 如果
build缺失,说明运行实例还没切到新版本 - 如果
build存在但route_surface缺路由,说明当前实例加载的仍然不是完整协议面 - 只有
build与route_surface都完整,才值得继续往 preview / execute 深挖
4. 当前阶段的明确边界
当前仍然要刻意坚持下面这几点,避免以后又滑回“假装可控”:
- 不直接 SSH 进节点手改
pending / dead-letter文件 - 不允许页面、CLI、Codex 各自定义不同的 replay / discard 逻辑
- 所有操作都必须落成正式
ops job - 单条 replay / discard 当前只允许针对控制面可见头部记录
这样未来从 head_only 升级到“远端全量记录正式投影”时,扩的是可见范围,不是重写整个治理模型
推荐以后直接以 ops_agent_protocol.md 为母本,把 delivery_queue 从 heartbeat 快照扩到控制面操作面。
正式要求应该固定成:
- Agent 只负责补发与执行
- 控制面负责查看、聚类、重放、丢弃
- 页面、CLI、Codex 不允许各自发明不同的死信处理逻辑
二十二、Playbook Run 必须成为正式一等对象
当前仓库里 playbook 已经不只是“预留想法”,而是已经有:
- catalog
- preview
- execute
- run detail
- run events
- rerun
- cancel
这说明骨架已经具备,只差把 contract 冻结清楚。
后续最关键的不是再补更多“脚本型能力”,而是把这几类正式收口:
onboarding.bootstraponboarding.acceptanceinspection.standardscene.logs.keyscene.logs.fullscene.diagnostics
也就是说:
- 接管验收
- 标准巡检
- 现场观察
都应该优先变成 playbook run,而不是继续散落在:
- 页面按钮
- Driver Action 临时逻辑
- 手工命令
推荐以后直接以 ops_playbook_contract.md 为正式母本。
这样后续:
- 页面点“标准巡检”
- CLI 执行“现场观察”
- 海外 Codex 驾驶员决定“先验收还是先抓日志”
最终看到的都是同一类对象:
- 先
preview - 再
execute - 最后围绕
playbook run / playbook events观察
这样控制面才算真正从“按钮集合”进化成“正式编排平台”。
二十三、单脑控制面最终要固定成 4 张操作面
Node Agent、Release Hub、Driver、Playbook 这些听起来很多,但终局页面不应该把操作者淹没在概念里。
最终应该稳定成 4 张操作面。
其中“现场面 + 队列面”建议以后直接以 ops_observability_contract.md 为正式母本,避免后面又把这层能力重新拆散到页面局部状态里。
1. 接管面
负责回答:
- 哪些节点已经纳管
- 哪些节点只是集群可见但未纳管
- 哪些节点 token 过期
- 哪些节点 agent 心跳异常
- 哪些节点已经通过接管验收
这一面主要消费:
ops_agent_protocolmanaged nodesbootstrap-planonboarding.acceptance
2. 现场面
负责回答:
- 哪些节点正在执行
- 哪些节点在线但未参与
- 哪些节点近窗有吞吐
- 日志回传当前是否关闭 / 关键 / 全量
- 当前应该优先巡检哪台节点
这一面主要消费:
overviewinspectionactivity-streamdriver_recommendationsplaybook runs
3. 队列面
负责回答:
- 哪台节点的回执队列有积压
- 哪台节点有死信
- 头部记录是什么
- 当前应该冲刷、重放,还是丢弃
这一面主要消费:
delivery_queuedelivery_queue.recordsflush / replay / discard
这里要持续坚持:
- 当前阶段允许
head_only - 但不允许退回人工 SSH 改文件
4. 发布面
负责回答:
- 当前默认 release 是谁
- 默认 gate 是
blocked / attention / ready - 哪些节点可以灰度
- 哪一批 rollout 正在执行
- 哪一轮 rollout 需要审批 / 暂停 / 回滚
这一面主要消费:
release_hub_contractlaunchpaddefault_rollout_gaterollouts
这样页面心智模型才会稳定:
- 接管面:先让节点进体系
- 现场面:看现场和巡检
- 队列面:处理回执不一致
- 发布面:推进版本与回滚
这 4 张面板不是 4 套系统,而是:
同一套控制面 contract 的 4 个观察窗口
二十四、新节点从“拿到机器”到“可以发版”必须走标准路径
后续不应该再写“某台机器特殊处理说明”,而要把新节点接入路径固定为下面 8 步。
1. 签发接入凭证
控制面生成:
- token
- bootstrap plan
- env 内容
- systemd 文件
- 启动检查命令
这一步之后,页面、CLI、Codex 都只能复用同一份 bootstrap 对象。
2. 节点安装 Node Agent
节点完成:
- 安装 agent
- 写入 env
- 启动 systemd
此时节点还只是:
agent installed
还不算已经正式接管成功。
3. 节点注册并进入托管目录
节点完成:
registerheartbeat
控制面能看见:
- 节点身份
- capabilities
- service_names
- delivery queue 摘要
这时节点进入:
managed candidate
4. 立即跑接管验收
控制面不应等待人工另开命令,而应立刻执行:
onboarding.acceptance
必要时自动追加:
inspection.standard
这样新节点接入不会停留在“能连上就算完成”,而会直接走到:
- 基础服务是否正常
- 日志是否可收口
- 诊断是否可采
- worker / api 拓扑是否正确
5. 如果有问题,先走现场面而不是手工 SSH
验收失败后,优先动作应该是:
- 查看
activity-stream - 打开
playbook run detail - 看
inspection - 看
delivery queue
只有 Node Agent 已明显不可用,才进入 SSH Rescue。
6. 验收通过后进入发布门禁
节点通过验收后,不应马上让它进入发布目标集合,而是先进入:
- release gate readiness
也就是明确判断:
- agent 在线否
- inspection 健康否
- delivery queue 正常否
- 是否允许 remote-agent rollout
7. 先进入 Worker / Control 正确角色集合
接入后必须明确:
- 它是
worker - 还是
control - 是否
effective_worker - 是否允许参与 rollout
不能让“纳管成功”和“自动进入发版目标”之间没有门槛。
8. 最后才进入 Release / Rollout
到这一步才允许:
- worker canary
- batch rollout
- advance / pause / rollback
这条标准路径的真正意义是:
新节点接入不再是一次性脚本,而是正式运维生命周期的开始
二十五、后续实现时绝不再回退的 10 条边界
为了避免系统越来越大以后又退回人工联调,下面 10 条边界建议直接视为终局铁律。
1. 不再把 git pull 当正式发布方式
正式发布只认:
- release artifact
- checksum
- rollout
2. 不再让节点自己决定版本
节点只能执行:
- 下载
- 校验
- 切换
- 回滚
不能自己挑版本。
3. 不再让页面、CLI、Codex 各自拼动作语义
所有动作必须收口到:
- action template
- playbook
- rollout
- delivery queue action
4. 不再把 SSH 当日常主链路
SSH 只保留:
- bootstrap
- rescue
5. 不再让回执队列治理脱离 ops job
无论:
- flush
- replay
- discard
都必须形成正式留痕。
5.1 不再让发布兜底路径退回“打开面板”
release_progression 现在也应该遵守同一原则:
- 有明确 Launchpad 推荐时,优先走
publish_latest_worker - 已经存在默认 Release、但 Launchpad 还没形成明确推荐时,兜底也要直接走
create_release_rollout_worker
也就是说,不能因为“还没形成完整 launchpad 推荐”就退回到“打开 Worker 点状灰度面板”这种 UI 导航式动作。
6. 不再让“在线”直接等于“参与检测”
必须明确区分:
- 在线
- 参与执行
- 在线待命
- 近窗有吞吐
- 负载同步中
7. 不再让页面自己猜跳转落点
后端必须给:
ui_intentrun_codeactivity_keyfocus_step_key
8. 不再让日志体系只靠全量大文本
必须分层:
- event
- playbook / job log
- runtime log
- diagnostics bundle
9. 不再把 root/www 混合工作区当正常运维模型
正式版只允许:
- release build side
- release deploy side
角色分清,而不是在目标节点长期维护一份脏 git 工作区。
10. 不再让“设计口径”和“实现口径”分裂
所以现在开始,后续新增能力都建议优先先补到:
ops_agent_protocol.mdops_driver_contract.mdops_playbook_contract.mdrelease_hub_contract.md
再去写页面和执行器。
这样你这套系统才会越来越像:
正式控制平台
而不是:
一直迭代的一组强力调试脚本
二十六、海外单脑控制面的总检入口也要正式化
上面整套设计如果只停留在:
- 有很多 endpoint
- 有很多检查脚本
- 有很多 contract 文档
那后面现场一复杂,还是会退回“先想想这次该跑哪个脚本”。
所以海外单脑控制面还需要一个固定的总检入口:
check_ops_center_stack.sh
它的职责不是替代:
check_ops_contracts.shcheck_ops_plane.shcheck_node_agent.shcheck_release_hub.sh
而是把这四层先按固定顺序收口成一次总览:
- 先看 API 与 contract registry
- 再看
link_snapshot / overview当前驾驶主线 - 再看托管节点、Node Agent、回执队列
- 最后看 ReleaseHub 与最近 playbook / activity
这样后续海外主机第一次接手现场时,默认起手式就固定为:
cd /opt/domaincheck/domain-api
bash domain-api/deploy/multi-region/check_ops_center_stack.sh http://127.0.0.1:8100
# 如果只想看最终收口,不展开全量原始 JSON
bash domain-api/deploy/multi-region/check_ops_center_stack.sh http://127.0.0.1:8100 summary
它本质上回答的是 5 个问题:
- 当前控制面跑的是哪一版 contract
- 当前最该处理的 driver recommendation 是什么
- 当前有哪些节点已经被正式接管、哪些还没进入稳定执行面
- 当前回执队列有没有死信或待补发积压
- 当前 ReleaseHub 是 ready / attention / blocked 哪一种
而在正式实现上,这份总检结果不应该只停留在“把几段 JSON 拼起来”,而是要直接形成一层可被人和程序共用的诊断输出:
diagnosis.surface_status说明总检表面层是不是完整联通diagnosis.automation_status说明海外单脑自动化执行面是不是已经真正可接管diagnosis.stack_status作为总检最终结论diagnosis.issues作为问题清单diagnosis.next_step作为默认下一步动作diagnosis.quick_commands作为现场立刻可复制执行的命令集合
这样一来,总检结果就不只是“读给人看”的说明书,而是后续:
- 海外 Codex 驾驶
- 后台按钮自动诊断
- 交接包导出
- 标准作业入口编排
都可以直接消费的正式 contract。
对应的正式入口也应该固定为:
GET /api/v1/ops/stack-diagnosis
CLI 总检脚本只是它的终端包装层,而不应该继续长期承担“总检规则真正定义者”的角色。
进一步说,CLI 默认动作入口也应该固定下来:
bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next
它不再要求海外操作人先从总检结果里人工抄:
action_codefocus_refrecommended command
而是直接消费:
diagnosis.next_step
然后统一进入:
driver-actions/resolvedriver-actions/execute-resolved
这一步很重要,因为它把“总检”和“下一步执行”正式接成一条链:
- 页面顶部总检入口
- 海外 CLI 单入口
- 后续海外 Codex 驾驶员
都不再自己猜“下一步该做什么”,而是统一信任一份正式 contract。
如果现场执行 stack-next 时拿到的是:
404 Not Found
那也不要把它理解成“设计不成立”,而应理解为:
- 运行中的控制面 API 版本还没升级到包含
/api/v1/ops/stack-diagnosis - 或代码已同步,但
domaincheck-api尚未重启
也就是说,后续现场排障里,404 属于:
- 部署版本未对齐问题
而不是:
- 总检 contract 本身设计错误
这一步很关键,因为它把“海外主机的第一反应”也变成正式 contract 的一部分,而不是继续依赖人工记忆。
也就是说,未来不管是:
- 人工运维
- 海外 Codex 驾驶员
- 后台按钮触发自动诊断
都应该优先先看这份总检结果,再决定下一步钻取哪一层。
只有这样,这套体系才真正具备:
单入口观察,分层下钻,协议不漂移
二十七、终局实现顺序不能再乱
为了避免后面继续一边联调、一边改口径,这套体系后续实现顺序必须固定。
建议严格按下面 6 段推进:
1. 先冻结 contract,再扩页面和执行器
优先冻结的母本应固定为:
- ops_stack_diagnosis_contract.md
- ops_driver_contract.md
- ops_job_contract.md
- ops_agent_protocol.md
- release_hub_contract.md
- ops_observability_contract.md
只有这些 contract 先稳住,页面按钮、CLI、Codex、Node Agent 才能围绕同一套结构化对象工作。
2. 再固定控制面的统一执行链
控制面日常动作主链必须固定为:
stack-diagnosisdriver-actions/resolvedriver-actions/execute-resolvedops jobNode Agent / rollout executorevent / complete / activity
也就是说:
- 页面不直接执行壳命令
- CLI 不直接拼最终执行接口
- Codex 不直接猜动作 target api
所有高层动作都先下沉为统一 resolve / execute-resolved contract。
3. 再把 Node Agent 执行面补齐
Node Agent 侧必须先补齐:
registerheartbeatpulljobs/{job_id}/startjobs/{job_id}/completejobs/{job_id}/events- 本地待补发 / 死信队列
直到这条链稳定之前,不应把“节点能 SSH 上去”误判成“执行面已经正式接管”。
4. 再把 Release Hub 收成正式发布面
Release Hub 后续必须坚持:
- 包只认 artifact
- 发布只认 rollout gate
- 批次推进只认正式 rollout / ops job
- 节点不允许直接
git pull
这样后续:
- worker canary
- controller rollout
- 失败回滚
- 灰度暂停
才会继续沿用同一个正式模型,而不是变回“发布时再单独写一套命令”。
5. 最后才是 SSH Rescue
SSH 不应再作为日常主链路,只保留给下面几类场景:
- Node Agent 尚未接管
- Node Agent 失联
- 运行环境损坏到 agent 无法自救
- release 切换半途损坏,需要紧急修复目录或 systemd
而且即使走 SSH,也要继续满足:
- 创建正式
ops job - 留下执行人、节点、动作来源
- 回写控制面审计链
6. 页面和 Codex 只能做“消费者”,不能做第二大脑
终局架构里:
- 页面负责展示和触发
- CLI 负责终端入口
- Codex 负责智能判断和编排
但三者都不能再自己定义:
- 新状态机
- 新动作对象
- 新门禁口径
- 新下一步推断逻辑
否则“海外单脑控制面”就又会退化成三套脑子。
二十八、上线前必须通过的收口验收
如果以后要说“这套系统已经进入正式长期可维护状态”,至少应通过下面 10 条验收。
1. 总检入口必须真实可用
下面命令必须能在海外控制面直接打通:
cd /opt/domaincheck/domain-api
bash domain-api/deploy/multi-region/check_ops_center_stack.sh http://127.0.0.1:8100 summary
而且结果里必须能给出:
diagnosis.stack_statusdiagnosis.issuesdiagnosis.next_step
2. 默认下一步必须可以直接消费
下面命令必须成立:
bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next http://127.0.0.1:8100
也就是说,现场不应再需要人工从总检结果里抄:
action_codefocus_refcommand
3. 所有标准动作都必须落为正式对象
后续任何标准动作都必须能落成:
driver actionops job- 或
playbook run
不允许再出现“后台按钮一按,后端直接去某台机器执行一个临时命令,但控制面里没有正式记录”。
4. Node Agent 接管和 runtime 心跳必须明确区分
控制面必须能够直接区分:
已接管仅运行态在线待接入心跳过期失联
绝不能再用“API 能看到节点在线”代替“Node Agent 已正式接管”。
5. 回执队列必须能治理,不只是能看见
必须能在页面或海外单入口完成:
queue-statusqueue-recordsqueue-flushqueue-replayqueue-discard-record
否则后续只要出现一次跨地域回执积压,就又会退回到手工删文件。
6. 远端日志回传必须可开关
多机联调时,海外控制面必须能够:
- 默认关闭,避免长期噪声
- 调试时打开,拿到远端关键或全量日志
- 调试结束后关闭
否则后续每新增一台大陆机器,都会重新回到“复制日志给 Codex 看”的低效模式。
7. Release Hub 必须可执行 canary 到 rollout
至少要能稳定完成:
- 选默认版本
- 看 rollout gate
- 发 worker canary
- 看结果
- 继续批次
- 异常暂停 / 回滚
8. 节点发布必须只认 artifact,不认 git pull
一旦进入正式执行链,节点侧必须坚持:
- 不
git pull - 不直接以运行目录为发布源
- 不允许 controller / worker 各自拉不同本地临时代码
9. 救援必须是正式例外,不是常态
如果某类动作每次都需要:
- SSH 上机
- 手工重启
- 手工改 env
- 手工粘日志
那说明它还没有进入正式执行面,不能算收口完成。
10. 页面、CLI、Codex 三端口径必须一致
同一条动作在三端看到的:
- 状态
- 下一步
- 风险说明
- focus_ref
- target_api
必须一致。
只有这样,后面你才不会再遇到:
- 页面说能做
- CLI 说不能做
- Codex 又判断成第三种情况
二十九、后续绝不能回退的 8 种反模式
后续无论现场多急,都不建议再回到下面这些模式。
1. 新增页面按钮,直接在后端执行 shell
这是最容易把平台重新打回“脚本集合”的退路。
正确做法应始终是:
- 先 resolve
- 再 execute-resolved
- 最终落成
ops job
2. 页面、CLI、Codex 各自拼 request payload
一旦三端自己拼:
action_codetarget_apifocus_refexecution chain
后面就一定会再次口径漂移。
3. 用 runtime 心跳冒充正式接管
“节点在线”不等于:
- Node Agent 已接管
- 可以标准发布
- 可以标准巡检
- 可以标准回执
这两者必须永远分开。
4. 把发布流程重新做成节点本地更新
正式发布链绝不应回退到:
git pull- 覆盖运行目录
- 临时复制文件
正确做法只能是:
- 构建 artifact
- 生成 rollout
- 由执行面标准接收和切换
5. 把 SSH 当成默认执行器
SSH 只能是:
- bootstrap
- rescue
- 临时过渡
而不能成为:
- 日常发布
- 日常巡检
- 日常日志拉取
- 日常服务控制
的默认方式。
6. 故障时重新回到“复制日志给 Codex”
终局目标是:
- 海外控制面统一拿日志
- Codex 直接消费控制面 contract
而不是每次故障又退回:
- 现场机器跑命令
- 人工复制粘贴
- 再让 Codex 二次解释
7. 新增机器时再写一份特殊部署说明
以后新增大陆 controller / worker,不应再写:
- “这台机器例外”
- “这台机器单独执行某个手工命令”
- “这台机器按另一套说明”
而应继续只走:
- bootstrap plan
- Node Agent 接管
- onboarding acceptance
- 标准巡检 / rollout
8. 因为赶时间,就跳过正式审计链
只要动作会改变远端节点现场,就必须有:
- 发起人
- 动作来源
- 目标节点
- 执行结果
- 回执事件
否则后续节点一多,你就再也无法回答:
- 是谁改的
- 为什么改
- 哪次改坏了
- 哪台节点没跟上
这 8 条如果长期守住,后面这套系统才不会越做越散,而会越来越像真正的:
海外单脑控制平台
三十、这一轮继续冻结的 4 组关键字段
这轮设计继续往前收口时,有 4 组字段必须正式冻结,否则后面最容易再次出现“页面一套、后端一套、Codex 一套”的漂移。
1. Execution Scene 的参与态字段必须固定
现在“节点在线”和“节点真正参与检测”已经被证明不能混为一谈。
所以后续 execution_scene 里每个节点都应该优先稳定返回:
participation_stateparticipation_labelparticipation_reason
推荐值直接固定成:
dispatch_activerecent_onlystandbyload_syncing
这样页面“参与检测节点”、CLI 摘要、海外 Codex 驾驶员才能直接区分:
- 在线但未参与
- 正在领任务 / 正在执行
- 近窗刚参与但当前已收口
- 控制面和现场仍在同步
这层后续建议直接以 ops_observability_contract.md 为观察面母本。
2. 远端日志回传必须从“有无样本”升级成“覆盖到哪些节点”
远端检测日志回传后续不能再只回答:
- 开了没
- 有样本没
这还不够支撑多机现场。
正式 contract 还要继续稳定返回:
source_node_summariescovered_participating_node_summariesmissing_participating_node_summaries
也就是说,控制面要能直接回答:
- 哪些参与节点已经有样本
- 哪些参与节点还没有样本
- 每台已覆盖节点当前拿到了多少关键行 / 全量行
- 最近一条样本来自谁
这样后面你不需要再自己翻日志去判断“到底是哪台机器没回传”,页面和 Codex 都能直接聚焦缺口节点。
3. Node Agent 拉到的任务包裹必须固定
Node Agent 后续不能只拿到:
actionpayload
然后自己去猜这是不是 rollout、是不是巡检、是不是日志采样。
正式 Agent Job Envelope 应继续带上:
job_typestep_keystep_titlepolicyrelease_contextfocus_ref
这样同一个 Agent 才能稳定承接:
- 标准巡检
- Worker 日志
- 诊断收集
- Release 部署
- Rollout 回滚
这层后续建议直接以 ops_agent_protocol.md 为执行面母本。
4. Release Hub 不能只显示版本,还必须冻结 manifest / readiness / preview
Release Hub 如果只停在:
- latest release
- rollout list
那本质上还是“看版本页面”,不是正式发布控制面。
接下来必须固定的 3 个发布对象是:
artifact manifestoperational_readiness.rowsrollout preview
它们分别回答:
- 这个包到底会改哪些服务、怎么验、怎么回滚
- 哪台节点为什么能发 / 不能发
- 真正发 rollout 之前预计会分几批、风险在哪
这层后续建议直接以 release_hub_contract.md 为发布面母本。
这 4 组字段一旦收死,后面再往下做页面、后端、CLI、Codex 自动驾驶时,就不会再反复经历:
- 先做一个能跑的版本
- 现场一复杂就发现字段不够
- 再回头改 contract
- 然后前后端一起返工
后续真正高效的节奏应该变成:
- 先定 contract
- 再做后端聚合
- 再做页面展示
- 最后接 Codex 自动驾驶
只有这样,Node Agent、Release Hub、OpsCenter 才会越来越像一个统一系统,而不是三块并排长大的模块。