源码解析全文与补充反例
Testing 源码解析完整展开 Manifest、候选身份、故障窗口、双源证据与验收结论,包含 5 段视频。它保留“未运行上游测试或真实模型”的验证范围,不把源码断言当成本次执行结果。
Durable Run 精校全文另附实际复现:Tool Step 已经 SUCCEEDED、Checkpoint 未提交的窗口可能重复通知。下文的 WAITING 结论仅适用于已有测试注入时 Step / Attempt 仍为 RUNNING 的条件,不能推广到全部提交前崩溃。
先把进程真正停掉
普通异常测试可能执行 finally、保留内存对象,甚至让旧连接帮助完成清理。这不足以检查“旧进程已经消失,新进程只靠持久化事实恢复”的路径。
M-Agent 的跨进程测试在明确的 CrashPoint 调用 os._exit,直接终止工作进程。测试进程随后重开 SQLite,检查残留的 Run、Step、Attempt 与 Checkpoint,再由新的 Registry、Store 和 Runner 恢复。旧租约仍有效时不能接管;测试通过受控时钟越过过期点,不代表生产环境可以跳过租约规则。1
这套方法的代价是测试更复杂,需要磁盘文件、进程退出码与独立记录;收益是恢复不会意外依赖旧进程的内存。
Checkpoint 前后必须成对测试
“能恢复”太笼统。同一个步骤在结果提交之前和之后退出,恢复动作应该不同:
| 故障窗口 | 新进程应观察到什么 | 代表性测试 |
|---|---|---|
| Model Checkpoint 后退出 | 复用完整响应,不调用模型 | test_second_process_reuses_checkpoint_without_model_call |
| Model Checkpoint 前退出 | 原 Attempt 不确定;策略和预算允许时产生新 Attempt | test_crash_before_checkpoint_replays_model_with_budget |
| Context Checkpoint 后退出 | 不重新读取 Provider,保留原资料与来源 | test_second_process_reuses_context_items_without_provider_call |
| Context Checkpoint 前退出 | 重新读取,外部内容可能变化 | test_crash_before_context_checkpoint_reinvokes_provider |
这些是 v0.5.1 测试源码中定义的断言,不是本次运行的 PASS 报告。成对测试的意义在于验证持久化边界,而不是只看最终回复是否相同。1
幂等工单:调用两次,外部更新仍一次
工单更新工具将稳定业务身份交给独立 ledger。首次更新已经落盘、Tool Checkpoint 尚未提交时终止进程,恢复可在冻结策略允许时再次调用同一工具。
测试同时检查两组事实:同一个 Step 下保留第一次不确定与第二次成功的 Attempt;外部 ledger 对相同业务身份只保存一条更新,并返回原结果。2
因此结论是“这个示例的外部去重协议在受控重放下保持一次有效更新”,不是 M-Agent 保证任意接口 exactly-once。只检查工具调用次数会误判重放,只检查 ledger 行数又无法证明恢复遵守了原 Step 与预算。
非幂等通知:确认不是再次发送
通知工具没有去重能力。已有测试先让独立 journal 记录通知效果,再在 Step / Attempt 仍为 RUNNING、Tool Checkpoint 未提交时硬退出。在这个窗口,恢复应停在 WAITING,不能再次调用通知工具。应用核实后提交 CONFIRM_STEP,才形成确认结果并继续 Run;Step 已经 SUCCEEDED 的另一个窗口不能沿用这个结论。
| 时点 | Run Store 的预期事实 | 独立通知 journal |
|---|---|---|
| 效果发生后、Checkpoint 前退出 | 有未确认 Attempt,无 Tool Checkpoint | 1 条 |
| 第二进程恢复 | WAITING,原因与目标 Step 可读取 | 仍 1 条 |
| 应用确认并继续 | 确认结果形成 Checkpoint,保留旧 Attempt | 仍 1 条 |
test_crash_before_tool_checkpoint_resumes_to_waiting 和 test_sqlite_confirm_after_crash_completes_run 分别覆盖恢复与确认路径。相反,应用选择 RETRY_STEP 时确实会再次调用工具,不能把它描述成确认或去重。3
并发、预算与流式输出也要检查历史
| 风险 | 不能只看什么 | 应检查的事实 |
|---|---|---|
| 两个 Runner 竞争 | 两边是否都返回一个结果 | 有效 Lease owner 只有一个,冲突者不能继续推进 |
| 旧调用迟到 | 新 Run 已成功 | 旧 owner 的权威写入被拒绝 |
| 重启重置预算 | 本次循环重试了几次 | 已持久化的 Attempt 仍计入原预算 |
| 流式失败后重试 | 界面最终显示了文字 | 新旧 attempt_id 分开,只有完整响应形成 Checkpoint |
| 取消到达时工具已发出 | 取消 API 是否返回 | 已发生效果被记录,后续 Step 被阻止 |
相关测试分布在 Lease、Retry、Stream / Cancel 套件。它们检查的是受控故障下的执行协议,不是远端服务在所有网络条件下的行为。4
怎样沿源码复查
在匹配 v0.5.1 的 M-Agent 源码环境、安装其开发依赖后,可先选恢复主路径测试。下面是复查入口,本次未执行:
pytest -q \
tests/test_m_agent_resume.py \
tests/test_m_agent_lease.py \
tests/test_m_agent_resolution.py \
tests/test_m_agent_stream_cancel.py \
tests/test_durable_support_agent_idempotency.py
完整离线示例把读取上下文、查订单、工单幂等更新、通知崩溃、WAITING、应用确认与最终结构化回复组合到一起:
python examples/durable_support_agent/run_acceptance.py
它使用确定性模型与测试数据,不需要真实模型凭据。不要把测试中的 journal 路径换成生产业务文件,也不要把预期的检查清单当成已经得到的运行报告。5
为什么已有测试,还需要参考验收包
上面的测试解释恢复应该遵守什么协议,但集成者还需要知道:这些行为是否在正在发布的 wheel 中成立?在哪种 Python、操作系统和安装环境中测过?必需检查有没有漏掉?失败能不能被别的绿色结果盖过去?
ADR-0042 为此选择 Reference Acceptance Pack:把独立 Reference Scenario、冻结的 Acceptance Manifest 和证据汇总组合起来,不让一个越来越大的客服示例承担所有证明。0.5 的 foundation-release-0-5 Profile 在同一候选下顺序重跑 Core、Durable、Session、Context、Routing、Eval 六个场景,既有场景不能借用旧版本 PASS。7
Manifest 冻结的不是一段“预期通过”的文本,而是下面这些身份与问题:
| 要回答什么 | 关键字段 | 不允许的替代 |
|---|---|---|
| 测的是谁 | source_commit、artifact_digest、sdist_digest、fixture_digest | 只写版本号或借用旧 wheel 的摘要 |
| 在哪运行 | environment | 用一台主机代表所有平台 |
| 必须测什么 | profile、scenarios、required_checks、required_cli_commands | 跑完后删除失败检查 |
| 谁负责证明 | Check 的 owner、public_seam、正负检查、两类证据键和 non_claim | 只列模块名或通过率 |
Coverage Matrix 把这些关系逐项列出,CLI 还会重新构造受支持的冻结 Profile 并完整比较。缺少 required 映射是缺口,不能用较高的覆盖百分比补足。8
沿公开入口跑一次候选验收
HOST 要从干净 checkout 构建 wheel 和 sdist,在仓库外全新 venv 安装本次 wheel 的 testing extra,不能用 editable install 或 PYTHONPATH=src。installed_identity() 返回精确发行物、fixture 与环境身份;CLI 检查实际安装成员和文件字节,并通过 uv build --offline --wheel 从 sdist 重建产品内容进行比较。这里比较产品成员,不要求两个压缩容器逐字节相同。9
下面是未执行的 Linux Python 3.11 HOST 复查模板。前置条件是已按上述方式准备候选与环境,PATH 中有 uv,离线构建依赖已缓存。PY、WHEEL、SDIST、MANIFEST、OUT 都应设为该次候选的绝对路径,输出使用新证据目录,不使用业务文件:
"$PY" -I - "$WHEEL" "$SDIST" "$MANIFEST" <<'PY'
import sys
from pathlib import Path
from m_agent.testing import foundation_release_0_5_manifest, installed_identity
wheel, sdist, target = map(Path, sys.argv[1:])
identity = installed_identity(artifact=wheel, sdist=sdist)
manifest = foundation_release_0_5_manifest(**identity)
with target.open("x", encoding="utf-8") as output:
output.write(manifest.model_dump_json(indent=2) + "\n")
print(manifest.digest)
PY
"$PY" -I -m m_agent.testing run \
--manifest "$MANIFEST" --wheel "$WHEEL" --sdist "$SDIST" --output-dir "$OUT"
预期是先冻结 Manifest,再运行离线场景、HOST 观测和变异自检,由检查结果决定退出码并输出内容寻址 JSON Bundle。身份或环境在入口被拒绝时可能尚无 Bundle,不是每次命令失败都能生成完整报告。内部六场景函数交回 checks、公开 evidence view 和 independent evidence,CLI 补齐 HOST 后调用 PackExecution.complete() 汇总。直接调用 Durable 场景函数时,其 HOST 项初始仍是 NOT_RUN,不能把函数返回当作完成了 HOST。10
故障脚本怎样留下双源证据
Testing 的 Durable Scenario 固定三个窗口:after_model_reservation、after_effect_dispatch、before_final_model_checkpoint,各重复三次。Adapter 在外部边界留下 sentinel,再以 os._exit(86) 终止真实子进程;父进程检查退出码,等待旧 Lease 过期,重开 SQLite,再通过 resume_run()、resolve_run() 和 inspect_run() 恢复及读取。11
其中 effect dispatch 窗口先把非幂等效果追加并 fsync 到独立 journal,再硬退出。恢复必须观察到 WAITING;Harness 以测试应用身份显式提交 RunResolution.confirm_step(...),不是 Runner 读取外部 journal 后自动确认。不要把这里的退出码 86 与客服示例的 17 混写:示例在 BEFORE_TOOL_CHECKPOINT hook 退出,Testing 场景在 Adapter 边界退出,注入位置不同。115
| 证据归属 | 代表字段 | 能证明什么 | 不能证明什么 |
|---|---|---|---|
| Run Store 的公开观测 | status、attempt_statuses、model_attempts、tool_attempts | 受控恢复的持久化状态和尝试数 | 外部通知服务真正发送了几条 |
| Scenario 公开摘要 | recovery_window_repetitions、waiting_semantics_observed、recovery_windows_authoritative_digest | 本次窗口重复、WAITING 检查和观测归属 | 未测试过的窗口 |
| 独立 journal/sentinel | recovery_windows_journal_digest、waiting_resolution_journal_digest | 本地测试外部系统记录的摘要归属 | 第三方 ledger 完整性或 exactly-once |
| Check Result | check_id、status、evidence_level、reason_code、evidence_digest | 一项声明的结构化结论 | 全 Pack 通过 |
| Scenario Bundle | manifest、execution、checks、execution_checks、两类 evidence、content_digest | 场景与完整 Pack 汇总、候选身份的绑定 | 原始 Store 备份或生产认证 |
Bundle 要求 checks 精确匹配本场景,execution_checks 精确匹配整个 Manifest,重叠结果一致;PASS 的权威证据键必须指向该项 evidence_digest,独立证据键也必须存在。结果拒绝非空自由文本 detail,证据映射限制为稳定键、数值、布尔、空值或 SHA-256 引用,不能任意塞入 prompt、凭据或错误正文。12
这有明确代价:Bundle 是最小脱敏证据,不是完整现场档案。Durable 场景返回摘要后会退出临时目录,原始 journal 不作为 Bundle 附件保留。作者推断: 摘要校验内容一致性,不独立认证原始记录真实,也无法重建已删除的 journal;双源在这里指运行状态和独立测试外部系统,不等于第三方审计签名。1112
让一条重复通知推翻成功终态
负例必须能推翻正例,否则绿色结果可能只是检查器没有检查。以下是未执行的纯内存示例,适用于已安装匹配版本的 Python 环境,不创建 Run,也不调用模型:
from m_agent.testing import reconcile_recovery_window
problems = reconcile_recovery_window(
{
"status": "SUCCEEDED",
"attempt_statuses": ["SUCCEEDED"],
"model_attempts": 1,
"tool_attempts": 1,
},
effects=["notice-1", "notice-1"],
model_dispatches=["model-1"],
)
assert "duplicate_or_missing_external_effect" in problems
预期是即使终态写着 SUCCEEDED,两条 effect 仍令公开对账器失败。独立 dispatch 数多于持久化 model Attempt 数则触发 model_execution_budget_reset。客服示例测试还在完整验收之后追加第二条通知 journal,重新运行只读 Eval,要求 notification_once 和整个报告变成 false。外部记录可以推翻成功终态,而不是由终态替外部系统作证。13
PASSED、FAILED、INCOMPLETE 与 ERROR
单项 Check 只有 PASS、FAIL、ERROR、NOT_RUN、INCONCLUSIVE;Pack 则从 CREATED/RUNNING 进入以下终态。PackExecution.complete() 按表中先后顺序判断,不计算平均通过率:14
| 条件 | Pack 状态 | 退出码 |
|---|---|---|
| 未声明结果、证据层级错配,或 required ERROR | ERROR | 3 |
| 没有上述错误,但有 required FAIL | FAILED | 1 |
| 没有 ERROR/FAIL,但缺 required 结果、含非 CONTRACT/HOST required,或有 NOT_RUN/INCONCLUSIVE | INCOMPLETE | 4 |
| 全部 required CONTRACT/HOST 都是 PASS | PASSED | 0 |
重复 check_id 等非法输入先被拒绝;CLI 非法调用用退出码 2,Bundle 完整性失败用 5。缺少 uv 导致无法观测构建是 Harness ERROR,候选构建完成但失败则是 subject FAIL。不能把所有非零退出都归咎于被测运行时。914
optional 也不能“救场”。ADR 规定 optional 不提升 required 结论;当前 Manifest schema 更窄,只有 required_checks,拒绝 required=False,没有独立 optional 容器。往结果塞入未声明的 optional.extra-check: PASS 得到 ERROR,而非合法 optional 成功。14
Release/Milestone 上层可以把 required FAIL 或 ERROR 投影为 aggregate FAILED,表示不能发布;这是 ADR 允许的发布结论,不回写单个 Pack 的 ERROR/3,也不覆盖子检查。7
verify 成功,只说明证据包校验通过
对 run 实际输出的一个 Bundle,沿用同一 HOST 环境和候选身份,可进行以下未执行的后处理;BUNDLE 为完整 JSON 路径:
"$PY" -I -m m_agent.testing inspect \
--manifest "$MANIFEST" --wheel "$WHEEL" --sdist "$SDIST" --bundle "$BUNDLE"
"$PY" -I -m m_agent.testing verify \
--manifest "$MANIFEST" --wheel "$WHEEL" --sdist "$SDIST" --bundle "$BUNDLE"
"$PY" -I -m m_agent.testing render \
--manifest "$MANIFEST" --wheel "$WHEEL" --sdist "$SDIST" --bundle "$BUNDLE"
inspect 预期给出公开 JSON,verify 核对身份、结构、双源引用、内容摘要及文件名,render 输出含 execution 状态和退出码的文本。Bundle integrity: PASS 是 verify 的完整性结论,不是验收 PASSED:合法的失败证据仍可以通过完整性校验。发布演示 render_release_demo() 才另行要求 PASSED。JSON 是权威数据,Markdown 是视图,改报告文字不能改变原结果。15
Manifest 声明的 inspect/verify/render 命令由 wheel 契约测试在 run 之后检查;Bundle 不会预先证明自己后来已经成功渲染,避免循环证明。8
恢复与自检:设计要求不等于已实现证据
Pack 的 .core-lifecycle-running.json 与业务 Run Store 是不同边界。它保存带摘要的 Manifest、execution 及可选 Bundle;恢复必须身份一致,损坏状态不能被忽略。有效状态中已有完整 Bundle 时,原子发布可修复截断的输出文件,不是从坏 JSON 猜回原结果。16
源码核对还留下三项不能用设计承诺覆盖的实现边界:
- 预算证据的范围: 当前 Durable 的
budget_fail_closed_observed和预算摘要复用整体恢复clean与 recovery digest,三次预算重复字段固定填入。不能据此宣称另行跑过三个预算耗尽后零 dispatch 的独立实验。 - mutation 的错误归类: ADR 要求未检出的 mutation 为 Harness ERROR,CLI Bundle 自检未检出会抛异常;但 Durable 的统一结果构造器将
mutation_detected=False映射为 FAIL。两者分类尚不一致。 - 六场景恢复粒度: ADR 要求只续跑未完成 Scenario;当前
_run_foundation_release_0_5()恢复 RUNNING execution 后仍顺序调用全部六场景,没有逐场景完成清单。core-lifecycle 的中断修复测试不能证明六场景已有逐项断点续跑。111610
这些是固定修订的源码阅读结论,本次没有修改上游实现。换成新 RC 后仍须完整重跑 required 场景;旧 Bundle 只供诊断,不能与新候选拼接成一次通过。7
测试证明到哪一层
| 层级 | 需要什么证据 | 不能替代什么 |
|---|---|---|
| CONTRACT | 离线确定性公共契约检查 | 真实模型端点兼容性 |
| HOST | 干净环境安装发行物后的真实进程与存储运行 | 任意部署环境的有效性 |
| PROVIDER | 明确端点与能力组合的凭证门控检查 | 所有供应商、所有配置兼容 |
| FIELD | 应用团队的真实部署与外部系统验收 | 超出该部署的通用保证 |
本文依据源码解析梳理测试方法,不新增上述层级的运行证据。低层检查不能升级替代高层结论;网页测试通过也不意味着 M-Agent 验收通过。6
平台矩阵也不能被单个 Pack 替代:0.5 冻结 Linux Python 3.11–3.14 的 CONTRACT、Linux 3.11 primary HOST 和 macOS 3.11/3.14 secondary HOST;缺格子要保留缺口,Windows 不在支持矩阵中。PROVIDER 另有显式授权入口和带身份的追加 revision,ADR 限定 fingerprint、配置漂移及最多 30 天的复用范围。本次不执行 live 命令,也不读取其凭据环境变量。78
历史 0.5 的 durable-run、session、context、eval 四工作负载基准仍可从基准归档查阅;发布证据的原候选为 ed7177d047a528070c23bc06c84fb10c7817d2fb,见原归档说明。这些结果保留原环境、样本与计时范围,不重新归属到 v0.5.1,也不构成生产容量声明。
回到项目的价值
这些检查让“可以恢复”变成具体问题:哪些完整结果被复用了?哪里产生了新 Attempt?通知是否被再次调用?旧 owner 有没有覆盖新事实?
M-Agent 的价值不是承诺故障不会发生,而是让应用在故障之后仍能检查已确认的事实,并在证据不足时得到明确的停止与处置入口。设计取舍见设计页,执行细节见技术页,版本脉络见迭代记录。
参考资料
Model / Context 跨进程恢复测试。源码解析第 12 章。 ↩ ↩