被否掉的、没底的、还没做的
《ops-agent 建造笔记》第 12 篇(终) 代码基线:v0.1.0-rc.1,提交
d897d2c95e70(2026-10-25 发布的候选版) 对照实现:core.py、execution.py、agent/tools.py、agent/schedule.py与发布/验收文档
0. 收个尾
前十一篇把这条链路讲完了:默认只读的地基(1)→ 确定性巡检(2)→ 通知去噪(3)→ unknown 与远程变更(4)→ 场景后验(5)→ 运行可见性(6)→ 预算与退避(7)→ 知识与记忆(8)→ 状态自身的备份恢复(9)→ 四种形态(10)→ 质量体系(11)。
这次把文章基线从漂移的 main 固定到 v0.1.0-rc.1 后,我想先分开两句话:代码候选版已经发布,受控变更正式版的真实环境验收没有完成。前一句是仓库和 Release 的事实,后一句也不能因为有了版本号就消失。
这篇不再逐个复述模块,算三笔账:哪些能力我暂时不开放,哪些取舍在这次复审后变得更具体,以及哪些答案还得等现场证据。
1. 我没有给系统开的几扇门
第一扇是自动重放 unknown。SSH 断线后,“没收到成功响应”和“没有生效”不是同一件事。这个系统接受的变更没有统一幂等保证,所以我保留了未核实 unknown 挡住同目标同命令重放的机制。人先核实,再记录 observed-ok 或 observed-failed;确实需要重做的,也重新走授权,而不是把重试藏在一次调用里。
第二扇是定时巡检里的自动修复。SCHEDULED_TOOLS 明确不暴露 approve/exec,发现问题后只能取证和提出建议。这个约束比一句“请只读”更实在。不过我也不能把它扩大成“所有没有 TTY 的进程都绝不可能写”:非交互 CLI 的受控执行仍可使用人事先批准的具体命令,RPC 也能由客户端提供确认回路。关闭的是无人值守巡检的自主写能力,不是取消全部脚本化执行。
第三扇是完整 shell 解释器。这个 tag 没引入 shell AST,而是在线性分段之外,增加 shlex 参数解码和有限的未引用展开识别。本轮回归里的引号拼接、glob/brace 造选项说明,只盯原始字符串不够;但把源代码解析成树,也不能直接知道执行时的环境、路径和所有外部程序副作用。
所以这版的选择更具体了:动态 shell 参数需要审批,已知写选项与 helper 不能走 R0,敏感读取还有不能被审批覆盖的拒绝。普通日志 glob 保留,本机实际匹配项有上限;远程内容 glob 因无法在本机核实目标文件而保守拒绝。我接受部分命令要换成明确路径,不能把所有拒绝都轻描淡写成“多确认一次就行”。
第四扇是通用流水线引擎。现有 workflow 有顺序步骤和前序结果占位符,scenario 有固定的预检、执行、后验与回滚证据;这已经是两种不同用途。任意 DAG、条件分支、补偿事务没有在这一版实现。我要先把这两条现有链路的证据做稳,再判断是否值得引入新的编排语义。
还有一批仍在路线图延后清单里:多渠道路由、向量记忆、知识图谱、Web/双向 IM、多用户 RBAC。没有实现不等于这些需求没有价值,只是我暂时不让它们占据当前闭环的位置。
2. 我到现在仍得承认的取舍
资源阈值还是静态配置。默认磁盘 85%/95%、负载每核 2.0/4.0,不是模型学出的基线,也不是经过所有机器验证的安全线。代码允许按目标在 targets.json 覆盖。我当前能负责的是比较规则和采集失败的语义,不能负责一个默认值对所有环境都合适。
调度对账还有 PID 复用边界。reconcile_runs() 会看记录里的进程是否活着,同号的新进程可能让旧运行被误认成仍在运行,导致中断少报。这里要和变更执行记录分开:execution._execution_alive() 在实际执行阶段还会核对目标锁持有者,不能把两个子系统都说成只看一个 PID。现有实现没有把跨平台进程启动身份做成更强的统一依据,这笔账仍留着。
备份是离线手动的。--quiesced 依赖人真正停写,工具不获取全局一致性锁,也没有自动备份轮转和外部保管。技能里有备份新鲜度与恢复演练步骤,但 playbook 不是已经部署的自动备份服务。我不能因为暂时没做自动化,就把手动说成天然更安全;频率、保留策略、加密与异地副本仍需要另外安排。
记忆同样保留了人工约束。当前是带日期的文本追加、有限长度召回和人工修改,不是会自动复核过时事实的知识库。这次补了历史/人工导入内容在读取时脱敏,却没有因此解决事实是否仍成立。秘密保护与事实新鲜度,是两笔不同的债。
“防误执行,不构成恶意 Agent 沙箱”也是一条要认真保留的边界。审批、只读工具集合、敏感路径拒绝都有代码和测试,不是纯口头约定;但同 UID 的进程、可编辑的本地配置、宿主确认策略和检查与执行之间的文件系统变化,都在更大的信任边界里。我不会拿几组绕过回归通过,就把它改写成强隔离已经成立。
3. 一个版本号,不能替我填完观察记录
v0.1.0-rc.1 的发布链已经走通:版本说明、CHANGELOG、README 同步,develop 到 main,再由 tag 触发四格验证与源码制品发布。309 项自检、223 项独立回归、10 项本地演练给了这次候选版一个可回看的代码和证据边界。
接下来最先要填的仍是本版 W8 的现场验收:专用 SSH 测试目标与服务、实际 provider、专用通知渠道、断线后的 unknown 核实、配置与权限恢复、服务后验,以及修复版本真实运行后连续 7 天观察。它们尚未完成,观察期不能从提交日或发布日自动起算。
这也不等于项目历史上从没连接过真实 provider。早期 M0 设计记录有端点实测;它证明的是当时那条链路,不会自动替后来的预算、通知、恢复和这次守门修复验收。历史证据保留,当前门禁按当前范围来算。
我会把宿主宕机时的外部心跳观察放在可靠性缺口里。单机进程再认真地写心跳,宿主彻底不可用时也不能自己发出告警;需要另一个已有主机或外部服务来观察,这不是把 daemon 再套一层循环能补上的。
再往后,HTTP/TLS 固定探针、外部告警接入、双向 IM 等按实际需要进入下一期。目前 DNS/TLS 等技能只是排查步骤,不能算成已经进入确定性巡检的第四组、第五组探针。
4. 系列之外的话
重读这十二篇时,我发现同一条规矩会在好几个地方出现。工具调用没有结果不自动重放,SSH unknown 不自动重放,已经输出流式片段后不自动重试;它们处理的对象不同,但都在防止一个无法确认的副作用被重复制造。
另一些地方在处理证据的缺口:采不到平台不算健康,规则摘要写不进磁盘不能由模型一句“正常”盖过去,状态备份的测试不能冒充十项业务恢复演练,候选版发布也不能冒充连续七天验收。
这些规矩也没有让我得到一个“对自己怎么死完全不敏感”的系统。PID 会复用,磁盘会满,单机通知会失效,真实环境还有没有碰过的路径。现在能做的是把可恢复的动作、保守的状态和可定位的证据接起来,把剩余限制写清楚。
一个人写项目,复审记录像下一次排查的交接笔记,路线图保留没有兑现的承诺,README 记录当前能接受的边界。我需要它们提醒下一次的自己:哪些结论有测试,哪些只是推演,哪些还没有答案。
十二篇在这里收尾。总览负责把模块和阅读路径放在一起;这些分篇保留各自的取舍。代码固定在这个候选 tag,后续如果边界变了,我希望能明确说出从哪个版本开始变,而不是让一串 main 链接悄悄替文章改写事实。