这一页不只列命令,而是把一次 OpenSpec 变更拆成“输入、产物、审查点和完成证据”。理解这条链以后,面对不同版本或不同 schema,即使文件名和快捷入口发生变化,也能判断当前应该做什么。
演练素材来自 OncallAgent 已归档的 2026-07-11-limit-qwen-embedding-batch-size。它解决百炼 text-embedding-v4 单次最多接收 10 条文本的问题:Provider 对更长输入透明分批并保持向量顺序,文档索引业务层仍一次提交完整 chunk 列表。
📷 [图片 token=IaKjbkK2HoNq0yxyRZncjcCLnJe(未能下载,见飞书原文)]
[!CAUTION] 这个 change 已经在当前仓库完成并归档。下面是历史复盘,不应在当前分支再次创建同名变更。真正练习时应选择尚未实现的真实需求,或在隔离分支回到变更前状态。
先看完整交付链
仓库调查
→ explore 澄清
→ new + continue,或 propose
→ proposal / delta specs / design / tasks
→ apply
→ verify
→ sync main specs
→ archive
→ wiki-sync
→ docs build
📷 [图片 token=Bscjbs2uaoRGuExICHVcyQuAnve(未能下载,见飞书原文)]
这条链不是要求每一步都由人手敲命令,而是说明每个阶段必须产生什么证据。Codex 可以执行大量机械工作,但不能跳过边界确认、规格审查和真实验证。
步骤一:从仓库事实开始,而不是先写方案
在仓库根目录先检查工作树、仓库规则和已经生效的规格:
git status --short
sed -n '1,260p' AGENTS.md
sed -n '1,220p' openspec/config.yaml
openspec --version
📷 [图片 token=CVZGb5JcVotTDBxcAOvcvc71ndc(未能下载,见飞书原文)]
针对本案例,还要阅读 openspec/specs/qwen-openai-provider/spec.md、apps/backend/src/super_ai/llm/provider.py、apps/backend/tests/test_llm_provider.py 和 apps/backend/tests/test_document_indexing.py。目的不是提前找一个文件去修改,而是先确认现有行为、责任边界和测试模式。
📷 [图片 token=TDVQbXqZsoS5qzx7MvlcTlUln3e(未能下载,见飞书原文)]
步骤二:先把需求写成可以审查的输入
历史案例可以整理成下面这段输入:
OncallAgent 通过 LangChain OpenAIEmbeddings 调用百炼 OpenAI-compatible text-embedding-v4。提供商单次最多接收 10 条文本。要求 Provider 对任意长度输入按每批最多 10 条进行拆分,并保持返回向量与输入顺序一致;文档索引层继续一次提交完整 chunk 列表。增加 11 条文本的 Provider 测试和 11 chunk 的索引回归。非目标是不修改 chunking 策略、HTTP/SSE、Milvus schema、前端,也不新增通用动态限流。
📷 [图片 token=Wi6MbMKptoeyakxls54cq5S6nCf(未能下载,见飞书原文)]
这段输入包含目标、背景、限制、允许修改的层和非目标。它仍然不是正式规格,但已经足以让 proposal 收敛范围。若只说“修一下 Embedding 报错”,Codex 很可能在索引服务、分块策略或重试逻辑上同时发散。
步骤三:需求模糊时先 explore
当问题还不清楚,可以在 Codex 中使用 /opsx:explore,或明确要求使用 openspec-explore。这一阶段允许阅读代码、规格和测试,比较方案并暴露未知项,但不应直接写应用代码。
探索完成的验收标准不是“生成了一篇分析”,而是能明确回答:真正的问题是什么;哪些行为已经存在;目标与非目标是什么;涉及哪些 capability;还缺少什么事实或决策。目标清楚后再进入正式 change。
📷 [图片 token=Z7Dsb4T8fonutkxPinMcEU6on9e(未能下载,见飞书原文)]
步骤四:选择快速路径或渐进路径
| 路径 | 适用情况 | 本地行为 |
|---|---|---|
| propose | 问题和边界已经比较清楚 | 创建 change,并按依赖生成达到 apply-ready 所需的全部 artifacts |
| new + continue | 希望逐份审查,或关键决策尚未收敛 | new 只建立脚手架;continue 每次创建一个当前 ready 的 artifact |
| ff | 希望快速创建一个新 change 的全部前置产物 | 本地 ff 自己包含 new,不应机械接在已经执行的 new 后面 |
📷 [图片 token=Q9xtbqdecokqIexyYNicKjhwnrg(未能下载,见飞书原文)]
快速路径示例:
/opsx:propose limit-qwen-embedding-batch-size
没有斜杠入口时,可以直接说:“使用 openspec-propose 创建该 change,先读取主规格、实现和测试,按本地 schema 生成 apply-ready artifacts。”
渐进路径示例:
/opsx:new <change-name>
/opsx:continue <change-name>
/opsx:continue <change-name>
# 持续到 status 显示 applyRequires 全部完成
📷 [图片 token=IyiSbAUb6onnwWxegOtcaActnfe(未能下载,见飞书原文)]
当前本地 Skills 的底层机制会调用 openspec status --change "<name>" --json 和 openspec instructions <artifact-id> --change "<name>" --json。Codex 应使用返回的 planningHome、changeRoot、artifactPaths 与 resolvedOutputPath,不能把某篇教程中的路径写死。
📷 [图片 token=WPyHbnlxHo2DYuxsReicw1LEnad(未能下载,见飞书原文)]
步骤五:逐份审查四类核心产物
**先审 proposal。**它应说明百炼批量上限为什么会导致较大文档索引失败,明确只改变 Provider 与相关测试,并把 HTTP、SSE、Milvus schema 和前端列为非影响范围。若 proposal 还在讨论具体循环代码,说明意图层和设计层混在了一起。
📷 [图片 token=IjvVbVcb3oH8UExZ47qcg1KXnNe(未能下载,见飞书原文)]
**再审 delta spec。**本案例修改 qwen-openai-provider capability,包含一条 Requirement 和三个 Scenario:
输入不超过 10 条时,使用一个兼容批次。
输入超过 10 条时,每批不超过 10 条,并按原顺序返回完整向量集合。
文档超过 10 个有效 chunk 时,索引任务最终为
succeeded,并写入全部 chunk。
**然后审 design。**关键决策是把限制放在 Provider 构造处:OpenAIEmbeddings(chunk_size=10)。索引服务仍调用一次 aembed_documents 并提交完整列表,由 LangChain 客户端透明分批。这样厂商约束不会泄漏到业务层,未来其他调用方也自动复用。
📷 [图片 token=Cdz0bZudEoZ7HcxBNYgcCIrgnfc(未能下载,见飞书原文)]
**最后审 tasks。**历史 tasks 将工作拆为四个证据点:配置批量上限;验证默认客户端;验证超过 10 个 chunk 的完整索引;运行质量门禁。每项都能判断是否完成,并且测试与验证没有被藏在“完成开发”一句话里。
产物审查的真正门槛是:每个 Scenario 能否指向一项实现动作和至少一种验证证据。只检查文件是否存在,无法阻止一套形式完整但内容空洞的 artifacts。
📷 [图片 token=MubybvSz5oXRTixzz23cfpvOnZe(未能下载,见飞书原文)]
步骤六:进入 apply,但持续允许规格修正
/opsx:apply limit-qwen-embedding-batch-size
本地 apply Skill 会先读取 status,再调用 openspec instructions apply --change "<name>" --json,随后读取返回的全部 contextFiles。spec-driven change 通常会提供 proposal、delta specs、design 和 tasks,但其他 schema 可能不同,不能只读 tasks.md。
📷 [图片 token=FfP7bccJwoR1YaxwCiJc4zW2nEc(未能下载,见飞书原文)]
本案例的合理实施顺序是:先在 Provider 定义批量常量并交给 OpenAIEmbeddings;再补 Provider 层的 11 条输入测试;然后补索引服务的 11 chunk 回归。完成并验证一项后才能把对应 - [ ] 改为 - [x]。
如果实现过程中发现 LangChain 不保持顺序,或 Provider 配置无法覆盖异步接口,就不能为了勾完 tasks 硬写补丁。应暂停,更新 design、Scenario 或任务,再继续实现。OpenSpec 的价值正是让方向变化可见。
📷 [图片 token=PJk7bHdYcohKzdx2Bk0ceGeLnZb(未能下载,见飞书原文)]
步骤七:把验证写成真实证据
针对这一历史案例,相关检查至少应包含:
cd apps/backend
uv run python -m pytest tests/test_llm_provider.py -k batches_by_ten
uv run python -m pytest tests/test_document_indexing.py -k more_than_ten_chunks
uv run ruff check .
uv run pyright
uv run pytest
openspec validate --all
📷 [图片 token=Nn5jbeRnDoLgWExVKOAcfkfeneT(未能下载,见飞书原文)]
Provider 测试证明 11 条输入被拆成 10+1 请求,向量顺序仍为 0 到 10;索引回归证明业务层仍一次提交 11 个 chunk,并把全部结果写入 FakeVectorStore。两项证据合起来覆盖 design,但它们使用 fake client 和 fake vector store,不能被描述成真实百炼或 Milvus 集成测试。
📷 [图片 token=EM1Jbto12oiPVexLWnhc8zlWngg(未能下载,见飞书原文)]
随后使用 /opsx:verify <name>,从三个维度审查:Completeness 检查任务和规格覆盖;Correctness 检查 Requirement/Scenario 与实现测试;Coherence 检查代码是否遵守 design 和仓库模式。verify 是系统化审查,不是测试命令的别名,也不是形式化证明。
📷 [图片 token=MkhtbwfdFokge5xKxKfcm9XYneh(未能下载,见飞书原文)]
步骤八:把 delta 合入 main specs
/opsx:sync <change-name>
本地 sync 是 agent-driven 的智能合并,不是把 delta 文件覆盖到主规格。ADDED 新增 Requirement,MODIFIED 只调整目标部分并保留未提及场景,REMOVED 删除行为,RENAMED 处理名称变化。
📷 [图片 token=LDNPbsBRqoIx1uxrBS7cCWSKnEg(未能下载,见飞书原文)]
本案例的完成证据是 openspec/specs/qwen-openai-provider/spec.md 已包含 “Qwen embedding batch compatibility” Requirement 及三个 Scenario,同时既有 Provider 要求仍然存在。这个终态证明 delta 已沉淀到当前事实,但不反推本轮实际执行过某条 sync 命令。
步骤九:归档完整决策历史
/opsx:archive <change-name>
当前本地 archive Skill 会检查 artifacts、未完成 tasks 和 delta 同步状态,然后把整个 change 移到 openspec/changes/archive/YYYY-MM-DD-<name>/。.openspec.yaml、proposal、design、tasks 和 delta specs 都随目录保留。
📷 [图片 token=IABQb678wojVMBxNU3gcjfCYnQc(未能下载,见飞书原文)]
归档不是删除,也不是 Git 回滚。归档目录只用于追溯;未来要调整批量策略,应建立新的 change,而不是修改旧档案来改写历史。
步骤十:执行仓库特有的 wiki-sync
python3 .codex/skills/wiki-sync/scripts/sync_wiki.py archive limit-qwen-embedding-batch-size
npm run docs:build
📷 [图片 token=WD7KbjJKBopx7CxDttTcvRsnnMg(未能下载,见飞书原文)]
wiki-sync 与 OpenSpec archive 是两个独立动作。脚本会扫描并重建 active/archive 页面、总索引和 Sidebar,检查 delta 与 main specs、include 路径和页面集合。聚合页通过 @include 引用 OpenSpec 原文件,不复制第二份正文。
npm run docs:build 验证 VitePress 能否生产构建,但不能替代 wiki-sync 对 include 目标和规格同步状态的检查。这里的 WIKI 是仓库文档站,也不等于当前飞书知识库。
📷 [图片 token=Vn3Kb8i3yoR6FYxUGBNcamNqnkg(未能下载,见飞书原文)]