介绍一个 Agent 项目,最有说服力的方式是给读者一条能跑、能观察、能核对的路径。这篇用三个小实验,把前面讨论的 Runtime 能力串起来。
这是 Cognitive OS 技术系列第 8 篇,也是整组文章的实践入口。
这篇文章要解决什么问题?
本文围绕“用可复现实验串起整个系列”展开。阅读顺序是:准备与隔离 → 文件验收 → 群聊互动 → DAG 与失败 → 最小贡献 → 内容发布。先建立概念与执行顺序,再进入具体函数、异常分支和测试,避免把模块名当成理解系统的起点。
| 阅读阶段 | 要回答的问题 |
|---|---|
| 运行前先建立可复现的环境 | 先固定环境、源码版本与隔离目录,再区分确定性 fixture 和真实模型的验收责任。 |
| 实验一:沿文件任务验证执行结果 | 从一次请求获得任务 ID,追踪终态,并检查实际文件;随后扩展到附件与复杂产物。 |
| 实验二:验证角色之间的信息传播 | 先确认角色确实看见彼此发言,再用反例检查回应质量,避免只按消息条数评分。 |
| 实验三:验证依赖、失败与排障路径 | 用依赖实验观察等待与阻断,通过故障注入把不确定的失败缩小为最小复现。 |
| 怎样把实验转为可审查的贡献? | 将输入、操作和验收写成可核对证据,明确贡献范围,再回到各模块继续深入。 |
| 怎样维护文章、源码基线与发布? | 技术博客也是可构建的工程产物:正文、示例、链接和部署版本需要一起核对。 |
文中的源码行为以原文标注的 cca98b6 为阅读基线;涉及“应该”“可以”或后续演进的内容属于设计分析与验证建议。历史基准和故障记录不代表当前版本每次运行都会产生相同结果。
1. 运行前先建立可复现的环境
先固定环境、源码版本与隔离目录,再区分确定性 fixture 和真实模型的验收责任。
贯穿案例:用一张实验记录表连接请求与事实
本篇实验可以共用同一套记录方式。每次只验证一个主要假设,把输入、操作和验收分开,能降低模型随机性对诊断的干扰。
| 实验 | 主要假设 | 最小验收证据 | 失败后先查什么 |
|---|---|---|---|
| 文件任务 | 工具确实改变了指定工作区 | 任务 ID、终态、目标路径与实际内容 | 是否进入工具执行、路径是否一致 |
| 两人讨论 | 后发言者读到并回应前文 | 输入记录与对具体反例的回应 | transcript 是否传播、轮次是否仍有效 |
| DAG 依赖 | 下游不会提前使用缺失结果 | 阻塞期间的状态与失败传播 | 依赖图、结果绑定和调度时刻 |
| 附件任务 | 上传内容真正进入处理链 | 转换结果与模型可见输入 | 上传、转换还是输入装配失败 |
例如文件实验失败,先用固定内容与固定路径复现,核对任务记录与文件系统;不要同时换模型、改工作区、增加角色。一次改变多个因素,即使成功,也无法判断哪个变化修复了问题。
成功之后再做一个相邻失败样本:文件任务可使用一个明确不可写的测试目标;依赖实验可让上游返回受控错误。两种结果一起记录,才能说明系统不仅能走成功路径,还能在前置条件不成立时停止。具体故障注入应限定在隔离环境。
最后把实验记录转成最小复现:注明源码版本、环境、脱敏输入、预期与实际结果,并链接相关日志。这让一次本地探索同时成为可审查的 issue、回归用例或小型贡献,也把前七篇的系统概念落到了具体行为上。
准备一个独立工作区
按照仓库构建文档生成 CLI。Windows 示例:
cd backend
.\build.ps1 -Target cli
New-Item -ItemType Directory -Force state-blog-demo,workspace-blog-demo | Out-Null
.\build\cognitive-os-agent.exe --state state-blog-demo --workspace workspace-blog-demo serve 18300
在启动前创建测试目录,并在所选 state 中配置模型;也可以先用项目的 mock 模式理解调用链。mock 用于确定性离线验证,不能用它的固定回答评价真实模型的理解质量。
服务启动后打开 http://localhost:18300,在模型页面配置你使用的接口和模型;配置项与本地模型接入方式见文末运行文档。密钥保留在本地配置中,不要放进准备公开的文章、截图或 issue。
Linux/macOS 按仓库 make 或 CMake 构建说明执行,对应二进制通常不带 .exe。下面的 HTTP 示例使用同一个 18300 端口。
把环境检查与应用检查分开
本地服务启动失败时,应先确认二进制从哪里来。运行中的桌面程序可能位于用户安装目录,而你刚编译的 CLI 位于仓库 backend/build;如果两者不是同一文件,就不能用 CLI 冒烟结果证明桌面 UI 已更新。记录 PID、映像路径、版本和 hash,可以排除“改了源码但旧进程还在运行”的混淆。
接着检查监听端口、配置目录与日志。GET / 返回 200 只能证明 HTTP 路由可访问,不能证明模型 Provider 配置正确,也不能证明 /v1/chat 成功执行任务。建议顺序是:`
二进制 --help 能启动
→ HTTP 首页可访问
→ /v1/tools 返回注册工具
→ 使用离线 fixture 提交确定性任务
→ 查询任务状态和执行步骤
→ 检查独立工作区中的产物
→ 最后接入真实模型与附件
每层增加一个变量,故障更容易定位。若一开始就同时配置新模型、上传视频、创建群聊和生成 PPT,失败可能出现在接收、提取、Provider、多轮规划、工具执行或完成核验的任意位置。
本地 State 与 Workspace 需要分别指定。State 通常包含运行配置、任务历史和持久知识;Workspace 是文件工具的操作边界。把二者放到临时目录可以在实验结束后清理,也能避免测试结果污染用户资料。测试脚本里用相对路径时,应先打印当前目录并构造绝对路径,防止 CLI 与脚本从不同 working directory 启动。
依赖选择也影响结果。build.ps1、build.sh、CMake 和安装程序可能调用不同 Python 或编译器。问题报告应提供实际编译命令、compiler version、目标平台和二进制路径。只写“我本地最新代码”无法让维护者重建相同环境。
数据和配置隔离避免把实验变成生产操作
测试 HTTP 服务时,应明确 CLI 使用的 state 根、workspace 根、端口和运行身份。一个常见错误是 UI 指向测试端口,CLI 却使用默认 state;用户以为自己检查的是同一个任务,实际读到另一份历史。启动命令可以打印规范化后的有效路径和端口,测试再核对响应中的服务实例标识。
用于演示的模型配置要单独保存,优先采用本地 fixture 或最低权限的测试凭据。运行 shell/SSH 工具时,workspace 并不一定限制外部命令可访问的所有路径;如果策略规则只约束文件工具,shell 可能仍能访问更大范围。演示机器应使用临时用户和虚拟机,清理其测试私钥和环境变量。
State 目录可能存任务历史、长期记忆和模型配置。测试结束后不要随意删除用户默认目录;创建明确的 state-blog-demo、workspace-blog-demo 后,再按路径限定清理这些目录。清理前打印规范化绝对路径并确保它位于仓库测试根下,避免变量为空时删除意外目标。
跑本地服务时,如果 18300 已被占用,先查监听 PID 与映像路径。不要直接杀掉未知进程或改用某个随机端口后继续使用硬编码 URL。可以让服务启动时打印最后实际监听端口,再从脚本读取配置;测试设置独立端口时要同步修改 BASE URL 和文档命令。
Fixture 与真实模型的验收边界
离线 fixture 给出确定响应,适合检查解析顺序、任务状态、工具调用和超时。真实 Provider 测试能检查认证、模型路由、多模态兼容和服务端错误,但会受网络、限流、服务版本与随机输出影响。两者应分开报告,避免一次真实模型“碰巧答对”取代稳定回归。
一个四层测试矩阵可以这样组织:
| 层级 | 主要覆盖 | 典型断言 | 结果稳定性 |
|---|---|---|---|
| unit | parser、状态机、长度边界 | 动作数组、转移和错误分类 | 高 |
| integration | Runtime + 本地工具 + State | 文件、journal、取消和回滚 | 高 |
| HTTP e2e | API schema 与序列化 | 返回码、进度、终态和节点状态 | 较高 |
| Provider smoke | 真实端点、认证和模态 | 请求可完成、usage 与错误类型 | 较低 |
每层都应有明确的前置构建步骤和可独立运行命令。如果测试依赖上一步产生的二进制,应在失败信息中提示缺少的路径,而非脚本误报网络错误。端到端日志要留在临时目录并输出路径,方便在 CI artifact 中检查。
2. 实验一:沿文件任务验证执行结果
从一次请求获得任务 ID,追踪终态,并检查实际文件;随后扩展到附件与复杂产物。
实验一:让文件结果说话
向 POST /v1/chat 发送:
{"message":"创建 note.txt,内容为 hello","session":"blog-files"}
拿到 id 后,查询 GET /v1/tasks/<id>。不要只停在最终自然语言答案:检查任务状态、工具步骤、测试 workspace 中的文件和实际内容。
再读取 /v1/tasks/journal,观察持久任务记录。这一步把“可以执行动作”的演示升级成“可以核对动作与记录”的演示。
如果所用模型不按工具协议提出动作,Runtime 可能要求继续规划或报告未完成。遇到失败应保留模型协议、错误和步骤,而不是为了演示好看把失败当成成功。
用一条 HTTP 请求跟踪完整状态变化
提交 /v1/chat 后,建议立即保存返回 JSON 中的任务 ID、会话 ID 和 HTTP 状态。任务 ID 是后续查询 /v1/tasks/<id>、观察执行记录和关联日志的关键。若接口返回 409 模型未配置,任务可能根本未进入正常规划;若请求被接受却一直 RUNNING,就应该沿 Provider 或工具阶段查。
一次完整记录可以按如下结构整理:
提交:request_id / session / timestamp
接纳:task_id / queue status
规划:model id / round / raw format family
解析:action count / tool names / parse diagnostics
执行:tool start / exit code / duration / bounded output
验证:expected artifact / actual hash / validator status
终态:DONE / FAILED / CANCELLED / TIMEOUT + reason
这些字段不是要求把所有内容原样写到公共日志。提示词、附件正文、SSH 凭据和授权信息可能敏感,诊断数据应使用脱敏摘要、限制保留期并避免记录密钥。开发者通常需要知道工具类型、路径是否存在、退出码和错误分类,不一定需要上传整个文件内容。
进度事件可能被 SSE/WebSocket 推送,也可通过轮询读取任务状态。断线后应能重新查询当前进度,不能假设每个客户端都收到了所有事件。事件应带任务 ID、顺序号或可排序时间,前端才能丢弃重复或过期更新。当前是否提供这些保证需要逐个检查对应 API,不能仅由“有实时进度”推导。
把实验一变成可重复的验收脚本
界面适合观察过程,脚本适合固定成功标准。下面用 Python 标准库提交请求、等待终态,再检查工作区文件。先按前面的命令启动服务;脚本从 backend 目录运行,对应 workspace-blog-demo。它使用真实配置的模型,可能产生模型调用费用。
import json
import time
import urllib.request
from pathlib import Path
BASE = "http://127.0.0.1:18300"
def request(path, body=None):
data = None if body is None else json.dumps(body, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(BASE + path, data=data,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=10) as response:
return json.load(response)
def wait_task(task_id, seconds=180):
deadline = time.monotonic() + seconds
while time.monotonic() < deadline:
task = request(f"/v1/tasks/{task_id}")
if task["status"] in {"DONE", "FAILED", "CANCELLED", "TIMEOUT"}:
return task
time.sleep(0.25)
raise TimeoutError("本地轮询到期,请继续检查任务状态")
target = Path("workspace-blog-demo/note.txt")
if target.exists():
raise RuntimeError("note.txt 已存在,请为本次实验使用全新的工作区")
created = request("/v1/chat", {
"message": "创建 note.txt,内容为 hello",
"session": "blog-files",
})
task = wait_task(created["id"])
print("状态:", task["status"])
print("答复:", task.get("output", ""))
assert task["status"] == "DONE", task
assert target.exists(), "模型或状态声称完成,但文件不存在"
assert target.read_text(encoding="utf-8").strip() == "hello"
print("PASS:本次新工作区中的文件与任务结果一致")
为什么先拒绝已有 note.txt?否则上次遗留的文件可能让本次实验假通过。相同逻辑适用于 PPT:打开一个已经存在的旧文件,不能证明刚提交的任务完成了新版本。
上面本地轮询超时只代表脚本停止等待,不会自动取消后端任务。如果要停止任务,需要对该任务 ID 发取消请求。若服务启用 API 认证,request 还需按运行文档增加 Authorization;不要把真实令牌写进准备提交的脚本。
附录:创建文件实验的 HTTP 轮询细节
如果运行 /v1/chat 返回任务 ID,可以在 shell 中轮询任务状态。下面是便于人工检查的 curl 形式;Windows PowerShell 中 curl 有时是 Invoke-WebRequest 别名,需显式使用 curl.exe,或直接使用 Invoke-RestMethod。
curl -sS -X POST http://127.0.0.1:18300/v1/chat \
-H 'Content-Type: application/json' \
-d '{"message":"创建 note.txt,内容为 hello","session":"blog-files"}'
curl -sS http://127.0.0.1:18300/v1/tasks/任务ID
curl -sS http://127.0.0.1:18300/v1/tasks/journal
第一条响应中的 ID 应原样保存,不能假设每次启动都从 0 计数。查询终态后再打开 workspace 文件检查 UTF-8 内容。若服务启用 API token,就在请求头加入授权;不要把 token 复制进公共终端录屏或提交的 shell history。
正式验收脚本应设置单调时钟 deadline,并在超时后打印最后一次 task JSON。不要直接无限循环,也不要在客户端轮询超时后误以为后台任务已取消。urllib 请求级 timeout 仅限制单次 HTTP 查询;整体 deadline 由 time.monotonic() 维护,二者含义不同。
若最终状态是 FAILED,保存 task.get("steps") 或 API 实际提供的执行记录,关注最后一次 tool 和错误字段。若是 TIMEOUT,应核对时间预算从任务提交还是开始执行计;若 DONE 但文件不存在,检查 CLI --workspace 是否与脚本当前目录一致。打开相同路径时,先用 resolve/realpath 输出绝对位置,避免 Windows 驱动器或符号链接造成混淆。
附录:小型交付产物的验收清单
内容类产物可以按四个问题检查:
- 来源:本次任务生成的文件是否位于明确目标目录,而非拿旧版本冒充?
- 结构:文件能否被对应解析器重新打开,页数、章节、页面尺寸或媒体流是否合理?
- 内容:用户要求的主题、证据和格式是否覆盖,文本是否乱码或缺页?
- 呈现:渲染后是否有溢出、遮挡、黑边、音轨缺失或无法播放?
这四层检查可以自动化,也可以结合人工抽查。结构解析适合 CI;内容质量往往需要 rubric 和人工审核。PPT 的 XML 合法不代表每页可读,视频封装正常不代表内容是用户要求的场景,DOCX 能打开不代表表格没有跑版。
检查结果最好写成单独的 JSON receipt,包含产物绝对路径或相对路径、SHA-256、运行的 validator 与版本、页数或时长、通过/失败项和警告。receipt 不要混进文章展示给一般用户,但它可以随构建 artifact 保留,方便发布复核。产物修改后旧 receipt 应失效,重新校验新的 hash。
比较修订前后的 PDF/PPT 可以用页图 diff,但不同字体渲染和系统缩放可能制造像素差异。更稳健的流程先比较页面数量和文本,再人工查看接触 sheet 与高风险页面。对可编辑文件还需确认文字和表格是原生对象,而不是整页截图。
如何验证附件不只被上传
上传一张图片后,可以从任务记录追踪 attachment ID、MIME、文件大小、原始保存位置和解析警告;接着确认请求 payload 是否真的包含视觉输入。模型回答中的对象描述只能作为一个观察点,并不能证明所有视觉请求都由正确模型处理。最好用本地已知图片和预期属性做多次重复测试,再验证不支持视觉的模型是否返回清楚错误。
对 DOCX/PPTX/PDF,先构造每种文件都有明确唯一文本标记的最小 fixture,再检查提取器返回页面/段落文本是否完整。扫描 PDF 再单独测试 OCR 缺失;PPTX 表格和图片文字也要分开测试。文档里有大量嵌入图片时,纯文本提取结果应明确说明未分析的对象。
视频测试可准备 5 分钟样片,在 0、60、120、180、240、300 秒附近检查采样策略与时间戳,确认生成流程实际走的是视频帧和音轨路线,而不是将 PPT 图像回放成“视频”。若只用抽样帧,就应把结论限定为采样观察;要测试连续事件,需调整采样密度或增加针对性片段。检查音频是否转写、字幕是否被解析、帧率和分辨率是否满足模型限制。
对大文件还要加入资源和失败场景:最大允许文件、超尺寸拒绝、损坏 zip、缺少 ffmpeg、不可读路径、Provider 超时、请求取消以及临时目录清理。一个文件成功上传不应导致后续所有任务挂住。每个用例结束后,再上传一份正常文件验证服务仍然可用。
用日志判断“任务已完成”是否可信
检查任务时,可以把用户原始要求改写成逐项验收列表。比如“将技术项目整理成深度 PPT”至少包含:目标路径、文件结构能否打开、页数与内容覆盖、每页文字是否可读、备注是否符合讲解需求。日志里的“写入了生成脚本”只是前置工作;“python 命令退出码 0”也只是进程状态,后面仍需产物检查。
如果执行记录只显示目录枚举、源码读取和一次脚本写入,没有最后的生成命令,那么最终答案不应写“PPT 已完成”。如果有生成命令但返回失败,应将完整 stderr 传入下一次规划;修改后仍需重复生成步骤,而非停在“计划修复”。如果同一脚本有多处同类语法问题,要修复全部,下一次全量运行会暴露剩余问题。
一次合格的日志可以让人还原“为什么状态是 DONE”:产生了哪些动作、每步是否成功、结果如何与请求项匹配。Runtime 的工具失败恢复提示已经要求 Agent 读完整报错、处理同类错误、重跑原命令并核对产物存在且可读取。这个机制仍然是模型指导,不是不可绕过的静态检查;关键发布或不可逆操作应在执行管道中独立加验证门。
排查产品 bug 时,将用户描述转成固定输入和预期/实际差异。例如“群聊没有 Agent”可以拆成 Agent 注册表、session members、触发消息、model route、Agent 输出、IM 入库、UI 列表七个观测点。若 /v1/agents 里没有角色,先解决注册;若角色已存在但 room members 为空,问题在会话配置;若消息已入库但不显示,才是前端订阅或渲染。逐层观测可以避免盲目改 UI。
3. 实验二:验证角色之间的信息传播
先确认角色确实看见彼此发言,再用反例检查回应质量,避免只按消息条数评分。
实验二:让架构师与评审互相回答
先注册两位角色:
curl -X POST http://localhost:18300/v1/agents \
-H 'Content-Type: application/json' \
-d '{"name":"architect","role":"提出方案,并回应评审的问题"}'
curl -X POST http://localhost:18300/v1/agents \
-H 'Content-Type: application/json' \
-d '{"name":"reviewer","role":"检查边界条件,提出具体问题并核对回答"}'
创建群聊并记录返回的房间 id:
{
"name":"方案讨论",
"kind":"group",
"members":["architect","reviewer"],
"discussion_rounds":3
}
向 POST /v1/im/sessions/<id>/messages 提交:
{"content":"讨论如何给源码阅读任务设计完整性验证。请互相提问,并说明各自观点需要哪些证据。"}
重点观察第二轮:架构师是否真的回答了评审的具体问题?评审是否针对回答更新意见?单纯重复角色介绍不算有深度的讨论。轮数和输入管道由 Runtime 提供,观点质量仍需读者判断。
界面左侧可以看到当前角色和轮次,也可以停止。讨论期间发送新的用户消息,旧任务会被接管,迟到回复不能继续进入房间。尝试一次这样的补充要求,比只看一段顺畅的最终回答更能理解调度边界。
给群聊一个能检验深度的题目
“讨论架构”范围过大,很容易得到两段泛泛的介绍。可以把题目改成:
我们用字节偏移和 EOF 判断源码是否读完。
architect:提出一个可实现的最小方案,列出依赖的假设。
reviewer:至少给出一个会推翻该方案的反例。
第二轮必须回答对方上一轮的问题;第三轮列出已验证、未验证和仍有分歧的结论。
接着用一张记录表评价结果,而不只看是否产生六条消息:
| 检查项 | 你应保存的证据 |
|---|---|
| reviewer 是否针对方案提问 | 对应的原句与问题 |
| architect 第二轮是否回应 | 回答具体覆盖了哪个问题 |
| 是否出现方案修正 | 修正前后有哪些不同 |
| 是否承认验证缺口 | 结论对应工具证据还是推断 |
| 新消息是否接管 | 旧任务结束方式与迟到回复情况 |
可以再做一个对照:轮数设为 1 与 3,使用同一道题,记录耗时、模型调用数以及未回答问题数。这样你得到的是交互机制的观察,而不是仅凭一次感觉评价“多 Agent 更聪明”。真实模型存在随机性,比较质量时还需要多次运行。
4. 实验三:验证依赖、失败与排障路径
用依赖实验观察等待与阻断,通过故障注入把不确定的失败缩小为最小复现。
加一个 DAG 实验:让总结等待证据
先通过“实验二:让架构师与评审互相回答”中的接口注册 reader 和 reviewer(或把下面名字换成你已注册的角色),在 workspace-blog-demo 创建 evidence.txt,内容为一个你知道答案的小样本。随后向 POST /v1/flows 提交:
{
"nodes": [
{
"id": "read",
"agent": "reader",
"task": "读取 evidence.txt,准确摘录其中的证据,不要修改文件"
},
{
"id": "review",
"agent": "reviewer",
"task": "只做文字核对:根据上游 {{read}},列出事实、推断和缺失证据"
}
],
"edges": []
}
返回任务 ID 后,查询 /v1/tasks/<id>,查看 flow_nodes。{{read}} 会形成依赖;review 在 read 成功前应保持 queued。真实模型可能回复很快,手工轮询未必捕捉得到中间态,所以确定性验证仍应使用仓库的 test_flow_dependencies_e2e.py:它通过 fixture 阻塞上游,使“下游未启动”成为稳定可测的窗口。
再把上游文件名换成不存在的路径。预期不是 reviewer 猜测文件内容,而是上游失败、下游 blocked。这个实验把协作从角色名称提升到执行条件,适合用来讲解为什么群聊和 DAG 提供不同能力。
实验三:用测试观察失败
构建 CLI 与测试程序后,另开终端,从仓库根目录运行以下回归:
cd backend
python tests/test_im_discussion_e2e.py
python tests/test_completion_recovery_e2e.py
python tests/test_crash_recovery_e2e.py
它们使用自己的临时状态与测试进程:讨论测试检查互相引用、停止、接管和失败;阅读测试检查未到 EOF 的任务如何继续;崩溃测试终止其创建的 Runtime,核对中断识别与显式恢复。
这些用例使用本地 fixture,便于反复复现控制流。它们不会证明所有生产模型行为和所有外部命令都已被覆盖。
常见故障先从哪一层看
| 现象 | 优先核对 | 下一步 |
|---|---|---|
| HTTP 400 | 字段名与 JSON | /v1/chat 使用 message,检查响应错误正文 |
| HTTP 409 模型未配置 | 生效 state、路由与模型配置 | 按模型配置提示补齐,再重试 |
| 连接不到服务 | 端口、监听进程、启动日志 | 确认实际是 18300 且服务未退出 |
| 群聊没有角色 | 注册表与 members | 先注册,再检查房间成员 |
| 显示完成但无文件 | 工具步骤与新产物 | 判断是否只读、只检查环境或计划未解析 |
| 源码反复从头读 | 返回的 next_offset_bytes | 保留真实分页记录,检查是否忽略连续前沿 |
| 旧 bug 仍能复现 | 实际运行二进制 | 对照安装路径、构建来源,确认已更新并重启 |
这里最容易混淆的是“我已经拉了最新源码”和“桌面程序已经运行最新代码”。桌面启动器通常启动安装目录下的可执行文件,未必使用你刚编译的 build 目录。排错记录应指向实际进程。
给回归用例设计边界值
一个基础功能通常有正常输入、最小输入、最大合法输入和非法输入。文件读取需要空文件、单字节文件、UTF-8 边界、超过单页文件、无权限文件和不存在路径;群聊需要单人、重复成员、最大轮数、零成员和部分角色失败;Flow 需要单节点、链、菱形、重复边、未知引用、自环、长环和超长字段。
自动化测试应同时观察副作用。例如 Flow 输入非法应在任何模型调用前拒绝;工具参数非法应在文件被创建之前失败;模型失败后系统不得产生一个伪造的 DONE journal。错误响应字段还应可解析,不能只包含 HTML stack trace。
长时间测试适合检查资源收敛:重复创建会话并删除、提交一批任务并等待排空、运行几轮 context 召回,然后观察连接、线程、临时目录与完成历史是否回到上限范围。资源数不一定回到完全相同值,因为 allocator 会保留 arena;应测对象计数和增长趋势,而不是要求 OS 立即把所有 RSS 归还。
每个测试最好只处理一个明确的不变量。一个包含 HTTP、Provider、附件、群聊和发布的大型用例失败时,很难知道是哪一层。可以保留少量关键 e2e 证明连接真实,但大多数边界放在较快的 parser、runtime integration 和 fake provider 用例中。
从用户反馈到最小复现的推理步骤
“任务一直卡住”不是根因,它是一种用户可观察症状。先问最后阶段是什么、是否有状态更新、任务最终是否超时、日志里有没有 Provider 请求 ID。若任务已经失败却 UI 仍显示运行,问题在任务状态传播或前端;若状态 RUNNING 且最后事件是 vision provider start,问题更可能在网络超时和取消;若一直停在附件提取,需检查外部进程是否可终止。
还要区分“进程挂起”与“任务没有进度”。HTTP 服务仍能返回其他请求,说明进程整体在线;单个任务可能等待远程请求。所有 API 都无法返回且进程 CPU 100%,可能是死循环或锁等待;服务无响应且 CPU 接近空闲,可能等待阻塞调用或死锁。简单的 process CPU、HTTP health 和 task progress 时间线就能把搜索空间缩小很多。
固定复现时,先删去不相关变量:用相同文件重放但切换到本地 Provider,判断是否由模型接口;保留 Provider 但去掉附件,判断附件路径;换小文件,判断大小阈值;重启服务后复现,判断持久 state;用同一请求不同模型比较,观察模态能力差异。每次只改变一个因素,记录结果,避免将配置、文件和模型同时修改。
当故障只在一次任务出现时,保存原始 request id、运行二进制 hash、任务 journal 和错误输出。为了保护隐私,复制到 issue 前先对附件和凭据脱敏;仍应保留格式、字节范围、页数、采样时间戳和模型接口类别等能复现行为的信息。
附录:故障复现报告的模板
标题:实际现象 + 影响对象
版本:commit/tag、build 时间、运行二进制绝对路径
平台:OS build、架构、shell 和相关依赖版本
前置:模型 route 与工具配置类别(凭据脱敏)
步骤:从干净 state/workspace 出发的最短可重复步骤
预期:可观察、可自动判断的结果
实际:task ID、终态、阶段和错误摘要
附件:格式/大小/页数/视频时长,不要上传不必要的隐私内容
日志:错误阶段、exit code、关键 offset 或响应码
频率:每次、偶发概率、重启后是否恢复
复现报告应保留必要信息,但要清除 SSH password、API key、cookie、私有路径中的个人身份和文档正文。若路径本身对定位有用,可以替换根目录并保留相对结构。示例配置将 secret 写成环境变量名或占位文本,不要提交真实值。
如果仅在用户机器偶发,补充机器时间和时区、服务启动方式、进程 PID 与监听端口。日志中的 wall clock 可能因校时变化;计时分析最好同时保留单调时长。若从桌面启动器运行,明确它实际启动的安装目录二进制,这常常解释“源码已更新但现象未变”。
5. 怎样把实验转为可审查的贡献?
将输入、操作和验收写成可核对证据,明确贡献范围,再回到各模块继续深入。
三个实验可以组成一次简短技术演示
演示不要追求每项能力都实时调用付费模型。先展示源码和固定测试,再运行独立 workspace 的轻量任务:提交文件创建请求、读取 task journal、用本地 fixture 跑一条有依赖的 Flow,最后查看一个可复现错误如何保留日志。这样观众能看到控制流和证据,而不是等待网络结果。
在群聊演示中选一道可反驳的问题,让评审角色第一轮提具体反例,架构师下一轮回答。用聊天记录确认后发言者看到了前面内容,再结合 fixture 用例说明更新用户消息后旧回复会被拒绝。话术中应分清“角色读到了上下文”与“角色最终观点正确”。
在 DAG 演示中准备一个成功的 reader 节点、一个依赖它的 reviewer、一个独立节点。先让 reader 阻塞,证明 reviewer 尚未调用;释放后看引用替换;再让 reader 失败,看 reviewer 被标为 blocked。手工 HTTP 轮询可能错过瞬时 queued 状态,因此最好借助 fixture 在指定状态停住。
最后展示失败恢复时,使用一个可安全重复的本地脚本错误,不要在真实生产目录、SSH 远程机或外部付费服务上注入故障。错误恢复完成之后检查本次产物的 hash 和内容,讲清每个验证门的作用。这样比只展示红绿 UI 更能说明技术实现。
贡献前先缩小问题和风险
一条好的 issue 不必写很长,但要让维护者第一次尝试就能触发:项目版本与平台、最短操作步骤、预期与实际行为、任务 ID 和相关错误阶段。附件复现要描述格式与大小;网络故障应写接口类型和 HTTP 状态;密钥必须脱敏。若任务一直卡住,注明最后可观察的阶段以及多久没有变化。
PR 可以按“失败测试 → 修复 → 相邻反例 → 文档”的顺序提交。先有最小反例可以避免把修复误写成与原问题无关的重构;相邻反例确保新规则没有破坏另一个合法行为。例如修改 SSH 命令解析时,既测输入带多余说明文字,也测合法 IPv6 或用户名字符;修改完成判定时,既测模型解释操作,又测用户真正要求操作。
若一个问题涉及多层,应将每层变化清楚分组,避免一次改动看不出因果。issue 评论说明复现原因、代码修改、提交哈希、测试命令和仍未覆盖的边界。关闭 issue 时确认发布分支包含 commit,并且用户实际安装或部署的版本包含该改动;本地构建通过不代表远端用户已经收到更新。
对于生成文档、图片、PPT 或视频的改动,也应在 CI 中检查内容结构和链接。临时截图、导出 JSON、模型中间提示词与失败残留应放入临时目录并加到 ignore 规则;已交付源文件则要保留,避免清理脚本误删用户成果。
附录:如何把失败案例转成一条小 PR
第一步把用户反馈写成一个最小输入与可观察失败。比如“完整读取后末尾内容缺失”,建立多页 UTF-8 样本并仅在最后一页放置断言文本;第二步确保测试在旧代码上稳定失败;第三步修改负责维护连续前沿的模块;第四步加入邻近偏移与旧路径测试;最后记录 build target、测试命令和版本路径。
小 PR 的描述应说清问题触发条件、用户看到的行为、实现如何改变以及测试覆盖的边界。不要将“增加了 200 行测试”当作修复说明,也不要声称测试覆盖了所有 Provider。代码中的注释则解释不明显的不变量,例如为什么 task completion callback 结束前不能回收对象。
如果涉及多个 issue,最好每个独立问题一个 commit;如果几项改动共同维持一个不变量,也可以作为一个有逻辑边界的修复。提交后本地分支干净,PR 中不应包含生成目录、用户凭据或临时测试数据。CI 失败时,应依据原始日志修正并重新运行相关检查,而不是只编辑 workflow 把失败隐藏。
发布后还要核对远端状态:仓库 default branch 是否包含提交,Actions 是否成功,Pages 部署是否更新;静态站点测试通过不证明 GitHub Pages 已完成发布。用户实际打开的域名还可能受 CDN 缓存影响,检查 HTML 里文章长度、commit 链接或 sitemap 时间可以帮助确认部署内容。
中文内容、代码示例和无障碍检查
技术站点常有中英文混排、路径、命令和 C 代码。构建工具需保留 UTF-8,不要先按系统默认编码读取再重写;检查中文标题、全角括号和转义字符在浏览器渲染后是否正确。复制代码时应不带行号,代码高亮也不应改变原始空格和反斜线。
代码示例需在真实环境验证。shell 的单引号在 Windows CMD 与 PowerShell 中语义不同;Markdown 中的反斜杠换行可能在复制时丢失。文档可以分别给 POSIX、PowerShell 命令,或明确要求 Bash。Python 示例使用标准库时注明 Python 版本,项目命令则在对应目录运行并给出相对路径。
Mermaid 图要有足够的文字替代说明。屏幕阅读器可能无法解释 SVG 中所有边关系;在图前后用正文总结流程,在 SVG 中提供 title/description,并检查键盘和高对比主题下的可读性。文字色与背景对比需要满足可读水平,颜色不能作为成功/失败的唯一编码。
长文章目录应支持键盘聚焦和清晰的焦点样式。窄屏宽表格应能横向滚动,页面宽度不应被代码块撑开。搜索结果应报告匹配条目数量、键盘可访问,空结果有清晰提示。深色模式切换后,代码高亮、表格边线和 Mermaid SVG 也要复查。
阅读者可以如何复现文章结论
每篇文章开头可以列出预期时间和准备条件,末尾给出源码基线、测试命令、关键输出字段和失败时的诊断方向。读者不一定愿意一次运行百万任务;可先用小 smoke 复现状态与计算,再使用长命令重做正式数据。避免命令只在作者机器有效,例如路径、模型名或安装位置应替换成明确参数。
最小复现实验应先告诉读者预期看到什么。如果 DAG 示例读者不知道 reviewer 应该保持 queued,屏幕上的一串状态就没有解释;如果 Context 实验没有给出 token_used 的核对方式,显示出的数字也难以验证。每个步骤最好有可观察结果与一个“失败则检查”的提示。
完整复现不代表每个读者都能得到逐字相同的模型输出。可以把可确定部分交给 fixture,例如动作数量、文件内容、节点状态;把真实模型的开放性输出作为定性观察,并注明模型版本、temperature 和系统提示。这样文章既保留实操,又不会把随机回答伪装为严格性能结论。
读者提交复测数据时,建议要求他们保留原始 JSON、环境 manifest 和运行脚本 hash,而不是只发一张终端截图。截图可以展示 UI,却往往不含聚合口径、命令参数和失败样本。结构化数据便于合并和重新绘图,也能发现单位和采样错误。
一篇技术文章如何组织证据
正文可以沿着“问题 → 目标行为 → 数据结构 → 执行路径 → 失败反例 → 测试 → 当前边界”展开。每个章节负责回答一个具体问题。源码引用说明实现所在位置,回归命令说明读者如何检查,指标报告说明数据从何而来;三者互相补充,不能彼此替代。
技术细节要按可信度标注。源码直接能看到的是实现事实;由多个代码路径推出的是推断;还没有测试的方案属于建议。将建议写成“当前支持”会误导读者,尤其涉及视频解析、外部 exactly-once、生产吞吐或自动修复时。文章可以有判断,但要说明证据和适用范围。
错误案例也有知识价值。描述一次失败时,说明触发条件、实际日志和根因怎样定位,再给失败后的修复与测试。避免只给一段最终成功截图,因为那不能解释系统以前为何失败,也不能让读者复现边界。涉及用户数据时只保留能解释问题的脱敏字段。
代码片段应保持短而准确。可引用足够的核心函数、删去无关分支并注明是节选;若伪代码,不要把它格式化得像实际 API。示例命令需确认在标注的 shell 下能够运行,并说明工作目录、目标端口和预期输出。作者在新版本上再次执行,能及时发现路径或参数已过期。
本系列的边界与延伸方向
这组实验覆盖 Runtime 的若干可观察边界,却不能替代真实生产验证:不同 Provider 的协议和额度、所有附件格式与损坏样本、跨平台运行行为、大规模并发以及外部操作幂等性仍需要各自的测量和测试。
下一步可以将本地 fixture 变成带固定版本的兼容性矩阵,定期验证 JSON/DSML、图片输入、SSE 分块和异常超时;也可以把 artifact 产物校验纳入完成契约,要求新文件经过二次解析而不是只检查路径。群聊质量则需要人类可审查的基准集,记录未回答的问题和可验证结论,而不是让 Agent 对自己的“共识”打分。
贡献者若要继续研究,可以从一项小指标开始:新增一个缺失边界的失败 fixture,运行现有测试并比较 before/after,最后把最小复现和证据更新到对应文章。源码和文档都应绑定实际提交,后续变更也要同步更新对应片段。技术博客的价值来自可重走的验证路径,文章越长越需要清楚标注事实、推断和待验证方向。
从一个小而完整的贡献开始
一个可审阅的改动可以只解决一个失败条件,例如中文页尾的偏移处理。但它应包含触发输入、预期结果、修复、相邻反例和测试命令。无需为了一个小 bug 重构所有模块;也不要只新增一条永远返回成功的 mock 断言。
建议按这个顺序准备贡献材料:
- 固定一个最小复现,说明当前实际状态和预期状态。
- 指出失败发生在解析、执行、状态还是展示层。
- 在正确层修复,保留与上下游的接口语义。
- 用真实文件、状态或请求记录证明行为改变。
- 在提交说明中记录测试范围与仍未覆盖的情况。
提交说明应记录定位依据、修复效果与验证边界,使后续维护者能够复现问题并理解改动。
源码阅读基线:HTTP 字段与任务入口、依赖端到端用例、CLI 配置和启动。
可以从哪里参与
如果你喜欢 C 系统编程,可以阅读调度器的生命周期、引用和取消边界;如果关注 Agent 行为,可以增加真实失败的最小回归;如果熟悉文档、多模态或不同模型协议,可以提供带脱敏输入、格式与错误阶段的兼容性案例。
一个有帮助的 issue 通常包含:使用版本、操作系统、模型与接口类型、最小触发步骤、预期行为、实际任务状态、展开后的工具结果。不要提交密钥、SSH 密码或不希望公开的文件。
项目仍有很多值得探索的地方,例如多轮讨论的质量评估、更细的 Context 选择、外部动作的幂等设计和跨平台性能复测。欢迎先跑一个小实验,再带着观察参与讨论。
项目:GitHub 仓库、中文 README、运行与 API 文档。演示:真实操作录像。
6. 怎样维护文章、源码基线与发布?
技术博客也是可构建的工程产物:正文、示例、链接和部署版本需要一起核对。
一个可自动执行的站点内容检查
博客正文发布到 GitHub Pages 后,CI 应从 posts.json 构造站点并验证所有文章入口。检查标题与 slug 唯一、摘要非空、文章文件存在、内部 Markdown 链接可解析、锚点没有失效、canonical URL 使用固定路径。加入新文章时还应检查首页筛选、RSS 和 sitemap 都能看到它。
内容长度也可以作为诊断信号,而不是价值评分。比如在构建报告中输出每篇字符数、代码块数量、表格数量和 heading 层级,如果新版本文章骤减 70%,提醒作者检查是否误删;不应要求文章越长越好。对于你要求这次扩充篇幅的情况,可额外记录扩写前的基准长度,让审稿者确认每一篇达到目标范围。
浏览器回归应覆盖首页搜索、分类筛选、文章目录、深色模式、代码复制、键盘快捷键与窄屏导航。对 Mermaid 静态 SVG,检查 fallback 文本和代码源仍然可用。文章代码块的横向滚动要在手机宽度下测试,否则很长的命令会把整页撑出视口。
发布工作流还应检查第三方链接可达性,但不宜让每篇链接的短期网络抖动阻断内容构建。可以用 link checker 的非阻断报告或固定抽查;内部链接必须严格通过。若远端链接变更,优先指向版本化源码行或 stable 文件地址,避免文章上线后立即失效。
更新源码基线时如何防止文章过期
每篇文中固定链接到 commit cca98b6,让读者看到的是核对时的源代码,而不是随 main 分支移动的内容。这提高可重复性,但当文章描述新修复时,应同时提供该修复对应的 commit 链接。若全文始终固定到旧基线,却描述后来新增的行为,就会形成读者无法从链接验证的矛盾。
可以把每篇的源码基线与核对日期放在 front matter 或元数据里,构建时生成“本文按哪个提交复核”。更新项目后,自动扫描固定链接目标文件是否仍存在;删除或迁移路径时,文章可以提示新的位置。代码行链接容易随后续修改漂移,commit permalink 更稳定,但读者仍可从提交内检索函数名。
大型重构后需要做文档差异审查:检查函数名、状态枚举、端口、API schema、配置默认值、线程上限、测试命令和 benchmark JSON 路径。可以用简单 grep 找所有重复字段,但语义判断仍需要开发者。每次改代码不必同步重写整套博客;只更新受影响文章并注明范围即可。
引用数字要绑定原始文件,而不是文章中的手工副本。报告路径若因归档策略移动,可在仓库提供稳定 README 入口,文章引用入口页而不是带日期的临时报告。删除临时 Markdown 时,先全仓搜引用并确认构建后内部链接仍有效,避免页面空链。
静态部署为什么也要做版本核对
站点构建成功只说明本地生成目录可用。GitHub Actions 是否部署成功、Pages 的 source 是否指向 Actions、域名 DNS/CDN 是否刷新是不同层面。发布后可检查 workflow run、部署 artifact 和线上首页响应,再打开其中一篇文章确认新段落已出现。
浏览器缓存可能让自己继续看到旧文件。用带查询参数的 URL、禁用缓存或检查静态 HTML hash,可以确认服务端响应是否更新。若 Pages 尚未部署完成,不要通过本地 _site 的截图声称线上已更新;应把本地验证和远端发布状态分别报告。
长文章上线后,还要检查锚点和目录。标题重复会让自动生成的 ID 产生后缀;内部链接若直接指向中文标题锚点,浏览器编码方式也可能不同。测试可以从每个 TOC link 点击到目标,检查 hash 和可见 heading 匹配。
文章长度增加后仍要维护阅读体验
每篇文章增长到约三倍后,应该同步强化导航:在开头标出读者要解决的问题和预期收获;TOC 提供可跳转标题;长代码块前说明它要展示什么,后面解释结果;表格保持列数有限,手机上仍可横向滚动。只增加大段文字会让读者更难找到关键结论。
小节标题应表达信息而不是泛泛写“更多说明”。一段通常聚焦一个技术问题:调用顺序、状态不变量、失败反例或测试边界。重复解释同一项约束时,优先增加新的证据或案例;如果没有新的信息,删除重复段比扩写同义句更好。
每页字数长短还应照顾读者场景。想快速理解的人可以沿摘要、章节标题和图表阅读;想验证实现的人可以跳到源码基线和测试命令;面试准备者可以用末尾问答和反例练习。不同读者共享一份详文时,标题结构和可检索词比再加一段宣传文案更有帮助。
如何在本地验证一次发布候选
站点发布之前可按固定顺序做检查:
python build.py
python tests/check_site.py
npm ci
npm test
build.py 生成静态页面;站点检查确认文章清单、链接、元数据和 SEO 资源;Playwright 回归在真实浏览器中检查交互与窄屏布局。若依赖目录尚未安装,先按 README 安装固定版本依赖。测试失败时应保留 Playwright trace、截图和控制台错误,定位是站点构建、链接还是浏览器呈现。
发布前检查差异:文章正文是否只有预期内容变化,posts.json 的 summary 是否与更新后的内容匹配,README 是否更新阅读顺序或复现说明,临时构建文件是否被忽略。用 git diff --check 检查空白和冲突标记,用 git status 确认 _site、browser trace、临时 JSON 和个人环境文件没有被误加。
Commit message 描述内容修改而非“update files”。若八篇文章共同扩展一轮,可以一个 commit 表达这次内容发布;如果同步修了一个独立网站 bug,再分开提交,便于回滚和审阅。推送前再运行一次完整检查并记录 commit hash,Pages 的 workflow artifact 可关联到同一个版本。
部署后用线上 HTTPS 页面确认首页、八个永久链接、RSS、sitemap 和搜索。站点首页有缓存而文章内容已更新时,需分别核对响应内容与页面版本。若 GitHub Action 失败,先修构建或部署错误;不要通过手工上传 _site 绕开已配置工作流,否则远端状态与仓库提交会失去关联。
发布后持续维护的最小计划
每次项目发布后,维护者可以检查三类变化:API 或状态语义是否改动、基准数据是否仍代表当前版本、示例命令是否在干净环境运行。变更较小时更新受影响段落与源链接;重大架构改变时再重新规划整篇文章,而不是每次都复制一份日期版。
站点 issue 也应保持聚焦:内容错误、源码链接失效、测试命令过期、移动端排版与措辞建议分别标明。读者如果能贴出文章段落、当前项目 commit 和复现命令,维护者就能把文字问题转成可检查的改动。
可以在文章页显示最后核对的 Runtime commit,给长期读者一个判断依据。发布日期说明文章何时发布,源码 baseline 说明代码按哪个版本解释,两者并不一定相同。更新内容但保留原发布日期时,页面仍应显示最后修订时间,帮助用户判断信息是否新鲜。
延伸阅读与实践顺序
完成本文后,建议选取“实验二:验证角色之间的信息传播”中的一个分支,用固定输入记录预期行为、实际结果和差异。先验证一个具体不变量,再扩大任务规模;若结果不同,优先核对源码版本、配置与输入证据。