一次资料研究做得不错,下一次换个主题,往往还要重新交代怎么读、怎么比较、怎么引用。能否把这次有效的方法保存下来,让它在新会话里接收新材料,继续按同一套规则交付?这是我准备做 DSH Workflow Forge 的起点。
本文记录个人业余项目的源码核对与实现拆解阶段,尚未完成目标宿主的安装和模型试跑。DSH 接口固定核对到提交 22ddee3,对应 manifest 版本为 0.2.1-rc.3。文中的代码都是缩减示意;项目自定义类型、辅助函数和工具会明确区分,不把方案写成已经发布的功能或性能结论。
可复用的方法需要同时固定输入、执行边界和验证规则。
1. 想保存下来的是一套能换输入的方法
假设我们要比较几份技术方案。有效的过程可能是:分别提取事实,保留出处,检查缺了哪些材料,再按指定维度组织结论。主题、文章和比较维度每次都会变,提取与检查的次序则可以稳定下来。
直接保存对话,容易把上次的答案、临时路径和未说出的上下文一起保存。只留一句“参考上次的方法”,也无法明确说明上次到底做了哪些检查。我希望生成物能回答几个具体问题:输入有哪些字段?哪些步骤调用模型?材料不够时怎样结束?最终报告依据的是哪一版定义?
这里仍然有 Skill 的位置。Skill 适合描述适用场景、指导用户补齐参数,也适合帮助作者解释和修改候选流程。对于主要是写作风格、表达偏好或灵活判断的任务,一份写清楚的 Skill 就可能足够。
source-brief 多了一些希望程序负责的行为:材料数量有上限,失败材料要保留,汇总必须等提取完成,事实引用要检查,取消后要等资源清理。我的划分因此是:**Skill 帮助形成和理解方法,固定程序负责执行边界与验证结果。**模型可以提出新的定义,但日常调用不能临时改写已安装场景的控制流。
这也决定了项目的规模。第一版使用 TypeScript、DSH/Cordis、Ajv 和少量构建工具,执行交给 DSH 原生工作流。先交付一个能独立使用的资料报告插件,再判断自动生成它的作者插件是否值得继续投入。
2. 第一份代码先手写 source-brief
自动生成器的第一道难题,是知道应该生成什么。因此我的实施顺序会从手写场景开始:先验证工具注册、输入合同、原生执行和失败路径,再把已经明确的结构抽成编译器。
场景叫 source-brief,处理已经读取出来的文本。URL 和 PDF 的读取可以由宿主已有能力完成;首模板接收实际文本,不假设存在一个万能文件读取接口。它的参数保持很窄:
| 参数 | 含义与首版限制 |
|---|---|
topic | 本次研究问题 |
materials | 1~4 份材料,包含唯一 id、title、text,可带 sourceRef |
dimensions | 1~6 个不重复的比较维度 |
language | 输出语言,默认中文 |
minSuccessful | 最少可用材料数,默认等于全部材料数 |
Host 先统一换行、切分非空段落并分配 paragraphId,保存本次输入快照。公开输入是 text,脚本看到的是 paragraphs,两者分别有 inputContract 和 normalizedInputContract。这样,编译器检查字段引用时不必猜测运行前偷偷增加了什么字段。
每份材料暂定最多 8000 个 Unicode code point,合计最多 24000 个;超出就要求拆分,不静默截断。这是控制首模板规模的产品限制,不能据此声称已经控制了 token 花费。
手写版本通过两组不同材料、材料不足和取消案例之后,才进入自动生成阶段。否则,生成器只会更快地复制一份尚未理解的实现。
minSuccessful 也不是一个可以藏在提示词里的容错偏好。默认要求全部材料可用;用户明确降低门槛,才允许基于部分资料继续,而且报告必须列出缺失材料。这样,“只比较了三份”属于可解释的输入覆盖情况,不会被汇总步骤悄悄改写成“综合四份资料”。
3. 报告最先需要设计的是证据结构
“输出带引用的报告”仍然太模糊。模型可以生成看起来合理的编号,也可以引用一段真实存在、却不能支持结论的文字。程序能检查什么,需要从数据结构开始界定。
提取 Agent 不生成最终证据编号,只返回三个字段:
type ExtractedFact = {
paragraphId: string;
quote: string;
statement: string;
};
type Finding = {
kind: "fact" | "recommendation";
text: string;
evidenceIds: string[];
};
collect_evidence 是项目中的固定函数。它在本次规范化材料里找到指定段落,确认 quote 非空且确实出现在该段落中,再按材料 ID 和事实序号生成 evidenceId。一份材料至少产生一条事实,且全部事实通过检查,才计入 successCount。空结果或任一错误引用都会使这份材料失败,同时保留材料 ID、输入序号和原因。
这个规则有意偏保守。第一版不再增加“从一批坏事实里挽救几条”的处理分支;等失败样例积累以后,再判断是否需要更细的策略。同一句话出现在两份材料中,也保留两份来源,不做会丢掉出处的语义去重。
汇总 Agent 只使用通过验证的证据集合。报告各维度的条目和开头的 conclusions 都采用 Finding[],避免最醒目的结论区变成无法检查的自由文本。事实项必须引用真实存在的证据 ID,建议项明确标成 recommendation;标题、正文、维度条目和结论都要非空,报告至少包含一条带证据的事实。
这套检查能挡住虚构编号、串错材料、漏维度和空报告。它仍然不能证明语义支持。原文说“在三个测试样例中可用”,报告却写“适用于所有场景”,即使引文完全匹配,结论也扩大了范围。所以首版还需要人工回看关键事实:引用是否被忠实解释,条件有没有被删掉,冲突资料有没有被掩盖。结构检查和语义判断各自留下结果,不能混成一个笼统的“已验证”。
4. 原生工作流接入,先把返回合同读准确
DSH 提供原生工作流能力,workflow-ptc 负责执行脚本,脚本再通过 agent() 启动子任务。我选择复用这个入口,模型环境与子任务生命周期都留在宿主侧,不再引入另一套工作流服务。Workflow subsystem 和 官方工具实现 是这一部分的阅读入口。
接入时有三个必须直接体现在代码里的事实:start() 同步返回 run handle,也可能同步抛错;run.result 是不会 reject 的 Promise;run.dispose() 是需要等待的异步清理。
引擎接收的 script 是 JavaScript 函数体,可以使用 await,最后返回 JSON;不能直接传 TypeScript 源码或 ESM 模块。下面展示的是 Host 侧调用段,fixedScript 则是另行生成并固定的脚本字符串。
// 缩减示意:外层已登记 ownedExecution,并连接取消来源。
// readBusinessResult 是本项目同步检查函数,不是 DSH API。
async function executeNative(exec, owned, fixedScript, checkedArgs) {
if (!exec.agent) throw new Error("CALLER_AGENT_REQUIRED");
let run;
try {
owned.controller.signal.throwIfAborted();
run = ctx.workflowEngine.start({
script: fixedScript,
meta: { name: "source-brief", description: "整理资料证据" },
args: checkedArgs,
parent: exec.agent,
signal: owned.controller.signal,
subagentProvider: "spawn",
maxTotalAgents: 5
});
const outcome = await run.result;
return readBusinessResult(outcome);
} finally {
if (run) await run.dispose();
}
}
readBusinessResult 先看 stopReason,只有 completed 才读取业务值,然后再检查业务 status。脚本成功返回 insufficient_input,也是引擎层的 completed;它并没有产出成功报告。cancelled、error、业务断言失败,以及清理失败也需要分别记录。完整包装层还负责保存原始 outcome 与 cleanupError,示意代码省略了这部分归档。
选择 spawn 是为了让提取任务不依赖父会话内容,所以提示词必须携带本次问题和材料。不过 fresh child 仍会继承 preset、cwd、模型等配置,父 Agent 的局部工具限制不会原样复制过去。自包含提示词减少上下文依赖,不能替代工具权限策略。Spawn 继承说明
agent() 的可用选项是 label、phase、schema、provider、model;其中 provider/model 指模型目标,子任务提供者由 start.subagentProvider 选择。不能凭想象加上 tools、context 或 timeoutMs 来实现隔离。PTC helper 源码
单次 start 的 maxTotalAgents 也只能收紧宿主已有上限。第一个接入实验会先确认目标 profile 提供了所需服务、spawn 支持结构化输出、调用身份真实可用,再尝试完整场景。源码中存在一个接口,只能说明可以怎样接入;它不说明每一份用户配置都已经装配好这些能力。
还有一个容易忽略的时序:原生 workflow/start 事件在 start() 返回前已经发出。自己的 STARTING/RUNNING 记录由 wrapper 主动写,不能拿到 run ID 后才开始等待这个事件。事件适合观察进度;最终值从 run.result 获取,因为 end 事件不携带 value。Workflow 事件合同
5. 并发先用两份一批,失败也占据原来的位置
首模板只有最多四份材料,不需要通用任务调度器。我准备把提取编译成每批两份,等待整批结束再进入下一批。这个限制属于当前脚本的调度方式,实际可执行并发还会受到宿主全局配置约束。
原生接口是 parallel(thunks),没有第二个 concurrency 参数。它返回与输入顺序对应的数组;普通子任务失败可以得到 null,helper 误用、非法 schema 等致命错误则向上传播。pipeline 是逐项穿过各阶段,没有“所有材料先提取完”的阶段屏障,和这里的汇总需求不同。PTC helper 源码
// 原生脚本片段;prompt、schema 和纯函数由项目固定生成。
const rows = [];
for (let offset = 0; offset < args.materials.length; offset += 2) {
const batch = args.materials.slice(offset, offset + 2);
const values = await parallel(batch.map(material => () =>
agent(makeExtractPrompt(material, args.topic), {
schema: EvidenceExtractSchema,
label: material.id
})
));
for (let j = 0; j < batch.length; j++) {
rows.push(values[j] === null
? failedMaterial(batch[j].id, offset + j, "NO_RESULT")
: checkedMaterial(batch[j], offset + j, values[j]));
}
}
这里不使用 filter(Boolean)。如果第二份材料失败,第三份材料仍然必须保留自己的身份;材料覆盖率和错误原因也要算进去。否则,一份漂亮的报告可能只用了原本四份材料中的一份,却没有告诉用户。
最多四次提取加一次汇总,得到的是 五次直接 agent() 调用上限。一个子 Agent 内部仍可能多轮请求模型或调用工具,所以这个数字不等于五次模型请求,也不是全部后代或 token 的硬预算。第一版不增加自动修复 Agent;无效输出按明确业务失败结束,避免以隐形重试掩盖成本和失败频率。
6. 取消责任从第一次 await 之前开始
如果只维护 activeRuns,插件卸载时会漏掉两类调用:还在读资产、尚未启动引擎的调用,以及引擎已经完成、仍在写结果的调用。资料任务的生命周期比原生 run handle 更长。
因此我会增加 ownedExecution:接收调用时同步检查 admission,在第一个 await 之前登记控制器和 settled Promise。登记持续到业务校验、异步清理和终态写入全部结束。activeRuns 可以辅助定位原生句柄,完整责任归 ownedExecutions。
调用信号、明确配置的运行期限和插件卸载,都汇入本次 AbortController。连接调用信号时先注册监听,再检查是否已经 aborted。期限到达只表示发起取消,后面仍要等待原生 result 与 dispose;它不能保证在同一时刻清理完所有资源。单独用 Promise.race 提前返回,无法替代这份责任。原生取消与清理合同
正常卸载需要把进度监听与异步排空放进同一个 Cordis effect。generator 按下面顺序收集 disposer,清理时会逆序执行并等待:先关闭 admission、取消并排空已接收调用,再解除监听。
ctx.effect(function* () {
yield ctx.on("workflow/log", onWorkflowLog);
// 其他进度监听也在同一组中 yield。
yield async () => {
accepting = false;
const pending = [...ownedExecutions.values()];
for (const execution of pending) {
execution.controller.abort("plugin unloading");
}
await Promise.allSettled(pending.map(x => x.settled));
};
});
allSettled 用于等完全部调用,各调用仍需记录自己的错误。这里 yield 监听返回的 disposer 很关键:它会把清理责任收入同一组。不同 effect 的清理可能并发,若把 ctx.on 留在外面,仅在独立异步 effect 末尾再调用一次 detach,无法保证监听在排空期间仍然存在。Cordis 生命周期 Fiber 实现
取消也需要一个明确的收口点:终态提交前同步复核 signal,随后冻结 terminalDecision。此前收到取消就提交 CANCELLED;之后的取消作为迟到通知,不再改写已选定状态。文件写入失败另行记为 FAILED 或 UNKNOWN,不能因为内存中选了成功就对外报告完成。异常退出留下的非终态记录则标成中断;一份 run.json 没有让工作流获得跨重启恢复能力。
7. 流程明确以后,再收进一门很小的 DSL
手写场景确定后,WorkflowSpec 才有了具体边界。它是本项目的 JSON 格式,用来表达这个范围内的方法。第一版只保留六类节点:
| 节点 | 能做的事 |
|---|---|
agent | 一个自包含模型任务 |
agent_map | 对有上限数组执行同一个单 Agent 任务 |
transform | 调用白名单中的纯 JSON 变换 |
assert | 执行固定业务检查 |
branch | 选择一个终结分支 |
result | 返回统一结果 |
顶层 steps 顺序执行;branch 只能出现一层,而且必须位于顶层末尾,两支都是以 result 结束的直线块。没有自由 edges、循环、嵌套 map、跨分支合流,也没有模型提供的 JavaScript 函数体。
限制分支的收益很具体:每条路径可以读取哪些已产生的数据,能够静态确定。进入分支前的结果对两支可见;分支内部只能引用自己的前序结果,不能读取兄弟分支中的值。节点 ID 全局唯一,map 的 item 只在其 task 内有效。
引用也保持结构化。例如,下列值表示读取已完成 collect 节点的 evidence 字段:
{
"ref": {
"scope": "node",
"node": "collect",
"path": ["evidence"]
}
}
路径只访问 JSON 自身字段与非负数组索引,拒绝 __proto__、prototype、constructor 等特殊片段。可选字段不能未经检查直接读取。常量使用 { "literal": ... },没有表达式字符串或 eval。
这套语义能覆盖资料提取、证据归集和条件汇总,维护成本也可以估计。以后若某个真实模板需要分支合流,再讨论可选输出与类型收窄;第一版不为尚未出现的场景承担一套通用图执行语义。
8. 四种 Schema 对应四个不同边界
把所有对象都叫 schema,很容易产生一种错觉:复制同一份定义就能从输入一直用到输出。核对 DSH 源码以后,这一层需要拆开。工具编写合同 Schema 类型
| 使用位置 | 实际表示 | 本项目怎样处理 |
|---|---|---|
| 产品 Spec、业务输入和结果 | JSON Schema | Ajv 做运行时校验 |
defineTool.parameters | ParameterSchemaSpec 字段映射 | 必填写在每个字段的 required 上 |
defineTool.output.schema | ValueSchemaSpec | 对象明确声明 additionalProperties,字段各自标必填 |
工作流 agent(..., { schema }) | object-root JSON Schema 子集 | 仅生成原生支持的关键词 |
例如,产品 JSON Schema 中的 required: ["topic"],映射到工具参数后变成:
parameters: {
topic: { type: "string", required: true },
minSuccessful: { type: "integer" }
}
片段省略了 materials 等字段;minSuccessful 保留可选,由规范化步骤补入默认值。工具参数的隐式根对象是开放的,因此 wrapper 仍要按产品 inputSchema 检查多余字段和跨字段关系。TypeScript 类型也不能替代这一步:外部输入和模型输出都应先作为 unknown 验证。工具 execute 返回规范 JSON,展示文本由 render 生成,调用者不必从一段说明里猜业务状态。
原生 Agent schema 支持的范围由源码定义:根必须是 object,支持 type、properties、required、items、enum、const 等;$ref、minLength、maxItems、minimum 不能直接塞进去。原生支持的 oneOf,产品首版也暂不开放,以减少字段引用的歧义。JSON Schema 子集实现
长度、材料数、维度完整性与引用关系由 limits/checks 明确执行。编译器遇到越界关键词就给诊断,不删除关键词后假装约束仍然生效。Ajv 开启严格模式和完整错误收集,关闭类型强转、默认值注入与额外字段删除;默认值在规范化步骤显式补入。Ajv 选项
9. 编译的产物还需要冻结它所依赖的实现
受限 DSL 的编译器可以很直接:先检查定义和资产,再建立作用域与类型信息,生成不可变 IR,最后使用固定模板发射脚本。最坏直接 Agent 数也在这里计算:顺序相加,map 乘最大材料数,branch 取两支最大值。
模型提出 Spec,变量名与函数体由编译器决定。提示词作为 JSON 数据编码,实际输入通过 start.args 传入。纯检查 helper 在构建时嵌入脚本,运行中的 PTC 脚本不依赖 ESM import、文件、网络、timer 或 Node API;文件保存和 Markdown 渲染在 Host 完成。产品验证器可由 Ajv 在构建期生成并打入场景包。PTC 执行边界 Ajv standalone
- 引用后面的节点、兄弟分支或不存在的字段。
- branch 后追加步骤,或某条路径没有 result。
- map 外读取 item,节点 ID 重复。
- 把任意函数体、未知 transform 或不支持的 Agent schema 关键词写入 Spec。
- 最坏直接调用数超过声明上限。
- 用户文本包含反引号、换行和看似代码的内容,导致生成控制流发生变化。
最后一类应该验证“文字始终是数据”,而不是禁止用户提交这些字符。错误定位到原始 Spec 的节点和字段,比只给生成 JS 的行号更适合作者修改。
到这里还没有解决“试跑过的到底是哪一份东西”。如果只对 Spec 求 hash,修改提示词文件或 runtime 后仍沿用同一个候选,旧试跑就可能替新代码背书。
所以 assets.prompts 内嵌提示词正文,schemas 也随 Spec 提交;节点只引用逻辑键,不接收任意磁盘或远程路径。specHash 覆盖规范化 Spec 与内嵌资产,候选身份进一步绑定实现快照:
draftHash = H(specHash, compilerVersion, runtimeArtifactHash,
checksHash, targetAdapterVersion)
draftRef 按 draftHash 定位。试跑加载候选冻结的 runtime/checks 字节,打包复用同一份字节;依赖变化或重新构建导致内容改变,就产生新候选。对象键按固定规则排序,数组保留顺序,时间戳不进入内容 hash。
参数变化和方法变化也据此分开。换一批材料、修改 topic,会改变输入快照及 inputHash;修改提取提示词、允许的节点或检查规则,则改变候选身份。运行开始后持有固定快照,不在中途重新读取“最新定义”。作者侧更新可以等已接收调用结束,外部卸载仍由前面的取消与清理路径处理。
构建身份还要避免自包含:计算 buildHash 时排除它自己、时间戳与后补的试跑摘要,归档文件需要校验时另记 archiveHash。这些 hash 证明的是资产身份与追溯关系;模型输出仍可能变化,hash 本身也不证明报告质量。
10. 作者 Skill 操作真实记录,不自己宣布通过
作者交互可以保持自然语言。用户描述任务,或选出一次可见的成功过程,Skill 帮他提取固定步骤、变化参数和失败条件,产生候选 Spec。这里使用的是用户描述、可见材料和工具结果,不依赖隐藏推理或假设能读取完整历史。
实际动作收敛到四个项目自定义工具:
| 工具 | 接收什么 | 留下什么 |
|---|---|---|
forge_validate | {spec} | 静态报告与不可变候选 draftRef |
forge_trial | {draftRef, inputs} | 本次输入、运行结果和检查记录 |
forge_package | {draftRef, trialRunIds} | 场景包与构建报告 |
forge_inspect | {kind, id} | 已保存定义、运行或构建的实际内容 |
validate 会保存候选,这一写入应在工具描述中说清楚。失败诊断使用稳定错误码,例如 UNKNOWN_NODE_REFERENCE、BRANCH_NOT_TERMINAL,同时给出节点和字段路径。Skill 再据此解释或修改,新定义产生新候选,已安装版本不会跟着对话漂移。
package 的门槛是静态检查通过,且同一候选至少有两种不同输入的有效试跑。记录必须匹配 draftHash、脚本与运行资产,打包器从实际记录读取结果,不接受模型传进来的 passed: true。
第二份输入应更换核心材料或主题,仅改几个空格没有说服力。这个门槛能检验参数是否真正进入执行路径,仍不能自动证明方法已经泛化。报告是否值得使用,要继续看证据与内容。
11. 生成的场景包怎样离开作者插件
我希望用户安装场景后,可以关闭作者插件,在新会话继续调用。因此场景分发物携带固定脚本、薄 runtime、检查逻辑、输入输出契约、Skill 和示例;运行期不加载作者插件或编译器,也不解释一整份可任意修改的 DSL。
自己的 runtime 和 validators 经 esbuild 进入 dist,DSH/Cordis 保持 external peers。独立场景仍然依赖宿主的 workflow engine、spawn provider 和模型配置,安装一个薄插件不会凭空补齐这些能力。esbuild external DSH Plugin Manager
DSH bundle 使用 dsh.bundle.patch 指向 Cordis patch;其中 insert 引用实际插件入口。这些字段应照官方合同生成,再检查 exports 和 files 是否包含真正要分发的资产。官方 bundle manifest 官方 patch
下列包名是拟用示例,并非已经发布的 npm 包。版本取本次源码基线;兼容声明必须在真实宿主验证后再发布。
{
"name": "@your-scope/dsh-scenario-source-brief",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json",
"./cordis.patch.yml": "./cordis.patch.yml"
},
"files": ["dist/", "assets/", "skills/", "examples/",
"cordis.patch.yml", "README.md", "LICENSE"],
"engines": { "dsh": "0.2.1-rc.3" },
"peerDependencies": {
"@deepseek-ai/cordis": "4.0.5",
"@deepseek-ai/dsh-tools": "0.2.1-rc.3",
"@deepseek-ai/dsh-workflow": "0.2.1-rc.3",
"@deepseek-ai/dsh-skill": "0.2.1-rc.3"
},
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
- insert:
- id: scenario-source-brief
name: "@your-scope/dsh-scenario-source-brief"
config: {}
包内还应包含 assets/workflow.js、固定 Spec、构建清单与脱敏 examples。实际材料、凭据和运行报告保存在插件配置的本地数据目录中,不进入分发包。
engines.dsh 一旦存在,会独立决定 DSH 版本兼容门禁,peerDependencies 仍承担依赖声明。外部插件不照搬官方 monorepo 的 workspace:* 或内部 private 配置。Boot 兼容说明
另一个要显式处理的动作是 Skill 注册。把 SKILL.md 放进包内,不会自动让宿主发现它;插件 apply 中还要通过公开 ctx.skills.register(...) 注册,并让注册与工具一起随插件生命周期清理。Skills 子系统
这个边界也可以结合此前的 Roam → DSH 插件文章阅读。Workflow Forge 进一步要考虑的是:当 Skill 和场景代码由工具生成时,注册、资源路径和卸载行为也要一起成为生成物的一部分。
打包、安装和实际可用分别记录。通过 Plugin Manager 安装以后,关闭作者插件,在新会话换材料调用,才完成独立性的验收。首版可以先没有专门 UI,工具结果提供参数说明、终态和产物位置即可。
12. 验证复用价值,要让失败也留在样本里
这个项目很容易做出一段顺畅演示:输入原来的材料,再生成一份差不多的报告。但我更关心换输入之后是否省去了解释,以及错误是否仍然可见。
验证会分成四层。无模型的静态测试检查非法定义;mock helper/engine 测试顺序、分支、null 与取消;真实宿主测试安装入口、服务注入和清理;最后用真实模型检查少量不同材料的报告。编译通过只能支持第一类结论,不能提前替后三层签字。DSH 自己的 原生工具测试 和 Host 生命周期测试 可以作为入口,理解如何围绕可观察行为验证资源责任。
首批固定五组案例足够启动:正常多材料、允许部分失败、有效证据不足、引用存在却不支持结论,以及新会话更换主题。另对包装层注入开始前取消、批次中取消、预检时卸载、结束写盘时卸载与 dispose 失败。空标题、空 findings 和空 conclusions 也要单独拒绝,不能让结构合法的空报告过关。
比较对象应包括重新描述任务、一份准备充分的 Skill 和场景插件。三者使用相同材料、模型和工具环境,记录补充解释次数、人工修正时间与可观察用量。创建模板、调试和维护也算投入;如果只是偶尔改写短文,插件很可能不划算。
本地记录围绕 runId、候选身份、inputHash、原生终态、业务状态、checks 和产物位置展开。未知用量保持未知,不用直接 Agent 数推算费用。只统计最后成功的尝试会隐藏修正成本,应该把失败、取消和版本变化一起保留下来。
文件存储目前足够:不可变候选、构建和每次运行分别有目录,run.json 通过同目录临时文件写完再 rename;一个 wrapper 拥有本次状态写入权。事件日志仅用于排查经过,不驱动业务步骤,也不被当作 checkpoint 回放。若以后需要恢复执行,必须另行设计已完成结果的重用与未知副作用的处理,不能给已有日志换个名字就宣称支持恢复。
13. 业余项目的交付可以小,但要能被别人接住
读完这些接口以后,我更愿意把第一步定为一个手写 source-brief:别人能够安装,换两组材料运行,也能看到一次失败的解释。这个交付本身就有价值。它稳定后,再增加受限 Spec、编译器、作者 Skill 和自动打包,每一层都由前一层的真实需求推动。
开源路线也应符合上游实际情况。本次核对的 DSH CONTRIBUTING 明确表示暂不接受外部 PR,同时鼓励独立插件、dsh-plugin topic、指南与 Discussions 参与。因而我会把重点放在自己的插件仓库和可复现样例上;若发现宿主问题,先去掉业务逻辑,保留固定版本、最小调用、期望与实际结果,再选择合适渠道反馈。
仓库说明需要讲清适用任务、真实测过的宿主版本、安装步骤、脱敏输入和失败边界。原生子 Agent 引擎归属于 DSH;这个项目的工作集中在方法表示、证据检查、调用生命周期、资产冻结和分发。把这条责任线写清楚,后续维护也更容易定位。
我希望下一次回到这个项目时,能拿出一份具体证据:一位没有参与原始对话的人,在新会话中换了材料,仍然获得结构一致、来源可追溯的报告;材料不足时,他也能知道应该补什么。如果这个小范围的承诺兑现了,再讨论更多模板、可视化编辑或恢复执行,会更有依据。
评论 0
还没有评论,欢迎留下第一个想法。