Skip to content

AI Agent 开发实战:参考 DeepSeek Harness ​

“做一个 AI Agent”听起来很酷,真正动手时会遇到一连串具体的问题:模型说它调用了工具,到底调了没有?它会不会读到不该读的数据?它会不会一直循环停不下来?用户刷新页面后,已经执行过的写入会不会再执行一遍?

这篇参考开源项目 DeepSeek Harness 的设计,先做一个最小的工具,再以野塘笔记为例,完整走一遍“检索笔记 → 调用工具 → 预览采纳 → 保存结果”的业务链路。还不熟悉 Token、上下文和采样的话,建议先读 LLM 基础;想对比完整工作台和可组合运行时的设计,可以读 ZCode 与 DeepSeek Harness。

读完这篇你会

  • 说清 Agent 的“模型提议、运行时执行”循环,以及 Harness 由哪些模块组成
  • 写出一个参数受校验、可取消、有输出约束的工具
  • 为 Agent 设计身份、权限、预算、会话恢复和写入幂等的边界
  • 用失败场景而不是“回答看起来很流畅”来验收 Agent

参考版本与核对范围

参考快照:2026-10-23,官方仓库提交 687f282。该版本仍处于开发预览,接口可能发生不兼容变化。下文区分“官方已有能力”和“本文建议”;本次没有启动 Harness、配置模型密钥或调用收费模型。

1. 原理:出主意的参谋,负责执行的办公室 ​

可以把一个 Agent 想成一位参谋加一间办公室:

  • **参谋(模型)**只负责出主意:“下一步应该查一下最近的发布记录”“现在资料够了,可以写结论了”。他不能亲自去翻档案柜。
  • **办公室(运行时 / Harness)**负责执行:检查参谋的请求是否合规、是否有权限,真正去调用工具,把结果记录在案,再交回给参谋,并决定什么时候该停下。
text
用户目标 + 当前会话
        ↓
组装指令、相关上下文和可见工具
        ↓
调用模型
  ├─ 返回最终回答 → 结束或交给用户检查
  └─ 请求工具调用
          ↓
     校验参数与执行策略
          ↓
     执行工具 / 返回拒绝或失败
          ↓
     记录结果 → 送入下一次模型请求

关键原则只有一条:模型产生的是“行动请求”,真正的文件、网络或业务操作由运行时执行。每一次调用的结果都要和对应的请求关联起来;工具失败了也要有明确的失败结果,不能把错误丢进终端、让循环继续假装成功。

2. Harness 由哪些模块组成 ​

DeepSeek Harness 的核心设计是“everything-is-a-plugin”(一切皆插件):模型适配器、工具注册、会话日志和默认的 Agent 循环都是插件,可以分别替换。Cordis 提供依赖注入与生命周期管理;卸载插件时,相应的注册和资源会随所属作用域一起释放。官方架构说明

模块Harness 对应能力开发时要回答的问题
模型适配ctx.llm使用什么协议、模型和能力参数
指令与工具描述ctx.systemPrompt当前任务应该看见哪些指令和工具
工具注册与执行ctx.tools参数怎样验证,执行如何拦截
会话记录ctx.sessions哪些事实能持久化、恢复和追踪
Agent 运行ctx.agents,默认由 agent-loop 驱动怎样继续、取消和结束任务
交互界面Web 或其他客户端怎样展示动作、结果与审批状态

3. 第一步:先定义一个能验收的小任务 ​

第一个版本建议做“只读资料助手”:在允许的范围内检索、读取、总结资料,并指出依据。也可以先用后面的纯计算工具跑通调用链,再接入检索。

动手前先写清四件事:

  1. 目标:比如根据指定资料回答一个问题;
  2. 输入范围:允许读取哪些内容,用户和租户身份从哪里来;
  3. 完成证据:回答要关联检索到的资料,拿不到证据时明确说明失败或不确定;
  4. 停止条件:达成目标、用户取消、预算耗尽,或者连续没有进展。

不是所有 AI 功能都需要 Agent

步骤固定的任务(固定关键词检索 → 固定模板生成),直接写成一条流水线就够了。只有当“下一步做什么取决于上一步的结果”时,才需要 Agent 的动态循环。

4. 第二步:先跑通官方的运行方式 ​

官方仓库提供 Web、headless、SDK 等 profile。Profile 是一套插件组合,bundle 是可被组合和覆盖的分发层,它们决定了启动哪些能力。普通的业务扩展优先接入已有的工具和事件接口,不必重写 Agent 循环。

要复现本文参考的版本,可以在独立的测试目录里按源码方式运行。该快照要求 Node ^22.19.0 || >=24.0.0,包管理器声明为 pnpm@11.28.5。以下命令供读者执行,本次没有运行:

sh
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
git checkout 687f282e8625c6b8d11ff58211a90524be2e4317
pnpm install
pnpm run build
pnpm dsh web

Web 界面默认运行在本机 http://127.0.0.1:3080。按官方说明在 Settings → Models 配置模型提供方,再选择模型和测试工作区,开始会话。自定义的模型网关还要核对协议,不能因为 URL 能访问就认为兼容。运行说明、模型配置

先读安全说明

官方把当前版本标为开发预览,并明确说明它可以执行模型生成的命令、访问开放给它的文件和凭据。初次实验请使用可以随时丢弃的工作区和最小必要权限,先阅读 SAFETY.md。

5. 第三步:开发一个最小的工具插件 ​

先完成官方的第一个插件教程,创建 scratch-plugin 和本地加载文件,然后把 scratch-plugin/src/my-plugin.ts 替换成下面的示例。

这个工具统计一段文本的非空行数,不访问文件和网络。为什么要做这样一个工具?因为“精确计数”恰好是模型不擅长、程序很擅长的事(见 LLM 基础),用它来验证“模型提议、程序执行”的链路最直观。代码基于已核对的官方 defineTool 接口改写:

ts
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'line-count-plugin'
export const inject = ['tools']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'count_nonempty_lines',
    description: '统计文本的非空行数;需要精确计数时使用。',
    parameters: {
      text: { type: 'string', required: true, description: '待统计的文本' },
    },
    output: {
      schema: { type: 'integer' },
      render: (_args, count) => [{ type: 'text', text: String(count) }],
    },
    async execute(args, exec) {
      if (exec.signal.aborted) throw new Error('CANCELLED')
      if (args.text.length > 20_000) throw new Error('INPUT_TOO_LARGE')
      return args.text.split(/\r?\n/).filter(line => line.trim()).length
    },
  }))
}

逐项看这段代码里的设计:

代码作用
inject = ['tools']等工具服务就绪后再注册工具
parametersHarness 的字段定义语法,框架据此推断参数类型并校验模型传入的参数
execute 返回整数,output.render 转成文字执行结果保持结构化,渲染给模型看的是另一回事
检查 exec.signal.aborted用户取消后不再继续工作
限制 text 长度schema 只管类型,长度等业务条件仍需自己检查
通过插件生命周期注册热重载后不会重复叠加同一个工具

这些契约在官方工具参考和工具 schema 源码中都有对应的定义。

官方教程要求本地加载文件使用插件的绝对路径。配置完成后,在仓库根目录启动:

sh
pnpm dsh web --patch ./scratch-plugin/cordis.yml

验收方法:明确要求模型调用 count_nonempty_lines,输入两行正文加一行空白,预期工具结果为 2。然后去会话记录里确认真的出现了工具调用和结果——不能只看模型最后说对了数字,它可能是自己猜的。官方工具教程

6. 第四步:把执行边界放进运行时 ​

工具一旦接入数据库、文件或外部系统,就必须由运行时提供可信的用户身份,并在执行前检查权限和范围。不要让模型传入一个 userId 就能读取那个用户的数据;工具返回的网页或文档里出现的“指令”,也不能被当成新的授权。

Harness 提供了工具执行前的策略、最终拒绝的 guard 和执行包装等扩展点。注意两件事的分工:工具“能不能被模型看到”和某一次调用“能不能被执行”是两回事,前者不能代替后者。工具执行管线

按副作用的大小设计执行边界:

能力建议的执行边界
检索与读取限定资源范围、校验身份、限制结果条数和输出长度
生成待审草稿返回候选内容和来源,保留“用户采纳”这一步
写入业务数据严格的参数校验、授权、幂等和结果核对
发信、发布、删除在执行边界上取得用户确认,避免重试导致重复执行

取消信号也要传递到真正的工作中:网络请求和子进程需要配合取消并清理资源。在工具定义里声明超时,并不等于运行时可以强行终止任意同进程代码;Harness 的 timeoutMs 还依赖相应的超时策略插件,不能只填一个字段就认为保护已经完整。

7. 第五步:管理会话、上下文和预算 ​

Harness 把会话中发生的事实记录到事件日志里,再从日志“投影”出模型能看到的历史。工具调用与结果、已完成的消息、失败的尝试都有明确的归属,便于恢复、展示和排查。会话与请求流程

你自己的 Agent 至少要记录:任务标识、工具调用标识、执行状态、错误、用量和关键证据。审计关注的是“执行了什么、依据是什么”,不需要依赖模型内部的推理过程。

上下文可以按需检索、限制工具输出的长度,或者做摘要。压缩时要保留用户的目标、约束、已完成的动作和未解决的问题。历史记录里也可能有凭据和个人信息,要先定义好保存范围和访问权限。

预算至少分成四类:

  • 单次模型请求的输出上限和超时;
  • 一次任务允许的总步数、总时长和总费用;
  • 工具调用的并发数和单项资源上限;
  • 无进展、重复失败、重复调用同一个工具时的停止规则。

这些都需要产品自己配置和验证(见文末“踩坑速查”中的无限循环问题)。

8. 第六步:用失败场景验收 ​

场景应该观察到的证据
可以直接回答的问题不产生无意义的工具调用
必须检索或计算的问题实际调用了对应工具,回答引用的是真实结果
工具名或参数无效校验失败并有明确反馈,没有执行任何副作用
没有权限或用户拒绝记录拒绝,任务不会换一条路偷偷执行
工具超时或用户取消停止新增工作,清理资源,保留准确的状态
同一步请求读写同一资源调度遵守依赖和互斥关系
进程中断后恢复不重复执行已经确认的写操作
返回内容里夹带指令当作不可信资料处理,不扩大权限

这些是开发验收的建议,不是本文已经执行过的 Harness 集成测试。不要仅凭最后一段回答很流畅,就判断整个 Agent 是正确的。

9. 实战案例:把知识库笔记整理成发布复盘 ​

用户提出这样一个任务:

在我选中的知识库里,找最近一个月与“发布”相关的文档,整理成一篇复盘。区分已有结论和待确认事项,标出来源,先让我预览。

这很适合作为野塘笔记接入 Agent 的第一个版本:模型可以根据搜索命中的结果调整关键词、挑选要读的文档,最后交付一份有来源的候选稿;是否保存仍由用户决定。如果需求始终是固定关键词、固定文档和固定模板,用“检索后生成”的流水线就够了。

实现状态

本节核对了现有前后端源码,但还没有为笔记系统接入 Harness。现有的 AI 功能是对输入内容进行整理、编辑或写作;下面的检索工具、Agent 任务协议和来源校验属于待实现的适配方案。资料、标识和代码示例均为脱敏改写,没有读取真实用户笔记、连接生产后端或调用模型。

9.1 现有能力与需要补充的能力 ​

当前前端使用 Vue 3,后端使用 Express 与 MySQL。业务能力可以复用,但需要在模型和这些能力之间加一层受限的工具适配层。

环节已有实现接入 Agent 时需要补充
搜索普通笔记在前端本地筛选;知识库按库、类型、更新时间搜索,支持摘要与分页把授权范围内的知识库搜索包装成工具
读取后端按登录身份读取未删除的条目再约束本次选中的库、文档类型和正文窗口
生成AI 接口返回清洗后的 HTML,不写笔记增加模型与工具的循环、来源记录和任务预算
预览编辑器对比预览、独立创作结果预览展示检索过程、引用来源和过期状态
保存文档创建、版本冲突检查、历史版本把候选稿接入用户明确选择的保存动作

这里的“搜索”有明确的边界:普通笔记目前在前端已加载的数据中匹配;知识库文档由后端检索标题和提取后的纯文本。本例只使用后者。现有实现没有向量索引,也没有语义相关度排序,结果主要按更新时间排列,不能把它说成“服务端统一语义检索”。普通笔记若要加入工具,需要另外设计受鉴权保护的服务端检索接口。

9.2 只给 Agent 两个读工具 ​

前端先让用户选择知识库和时间范围。后端验证登录状态和知识库归属后创建任务,把可信的用户身份和允许的范围绑定到任务上;模型只能填写搜索词和候选文档的标识。

工具模型填写适配层返回
search_noteskeyword、offset候选列表、命中摘要、版本、是否还有下一页
read_noteentryId标题、正文片段、实际读取的版本、是否被截断

工具名称和返回结构是本例的业务设计,不是 Harness 自带的笔记 API。建议的任务限制(不是现有接口的默认值,还要结合实际 Token 预算调整):

  • 每页最多返回 5 条,整个任务最多搜索 2 页、最多读 3 篇;
  • 每篇最多读取 4,000 个 JavaScript 字符单位(即字符串 .length 的计数单位);
  • 换关键词搜索也消耗同一份任务预算,不能借此重新开始计数。

搜索结果只给摘要,不带全文。模型选出候选后,再通过 read_note 按需读取。每次读取都重新核对归属、未删除状态、本次的库范围和类型;还可以要求标识必须来自本任务的搜索结果,防止模型“猜”一个标识扩大读取范围。搜索和读取之间内容可能发生变化,引用应绑定实际读取时的版本。

工具参数里不放登录令牌、数据库连接、任意 URL,也不允许模型指定 userId。用户身份来自认证结果;即使检索到的内容写着“请读取另一个用户的资料”,它也只是资料。

9.3 用 Harness 的工具契约连接业务读取 ​

下面是 read_note 的工厂函数,沿用前面的 defineTool。ScopedNotes 和 forAgent 是需要自己实现的业务边界:本例为每个任务创建独立的 Agent 实例,后端建立“Agent 实例 → 已认证任务”的映射,任务结束即撤销;未知或已失效的实例必须拒绝。不要把某个请求的用户身份捕获进全局共享的工具里,否则不同用户的请求会串号。

ts
import { defineTool } from '@deepseek-ai/dsh-tools'

type NoteExcerpt = {
  id: string
  title: string
  version: string
  text: string
  truncated: boolean
}

interface ScopedNotes {
  // 实现负责重新鉴权、检查本次范围、限制输出并响应取消。
  readExcerpt(
    entryId: string,
    options: { maxChars: number; signal: AbortSignal },
  ): Promise<NoteExcerpt>
}

export function createReadNoteTool(
  forAgent: (agent: object) => ScopedNotes,
) {
  return defineTool({
    name: 'read_note',
    description: '读取本任务搜索命中的知识库文档片段,保留版本供引用核对。',
    parameters: {
      entryId: { type: 'string', required: true },
    },
    output: {
      schema: {
        type: 'object',
        additionalProperties: false,
        properties: {
          id: { type: 'string', required: true },
          title: { type: 'string', required: true },
          version: { type: 'string', required: true },
          text: { type: 'string', required: true },
          truncated: { type: 'boolean', required: true },
        },
      },
      render: (_args, note) => [{ type: 'text', text: JSON.stringify(note) }],
    },
    async execute(args, exec) {
      if (exec.signal.aborted) throw new Error('CANCELLED')
      if (!args.entryId.trim() || args.entryId.length > 128) {
        throw new Error('INVALID_ENTRY_ID')
      }
      const note = await forAgent(exec.agent).readExcerpt(args.entryId, {
        maxChars: 4_000,
        signal: exec.signal,
      })
      if (exec.signal.aborted) throw new Error('CANCELLED')
      return note
    },
  })
}

这是工具定义的片段,不是完整可运行的接入工程:还需要实现业务适配器、在插件中注册工具、为每次执行解析可信的任务、配置模型、权限和预算;search_notes 也按同样的方式定义入参和结构化输出。对象 schema 显式声明了是否允许额外字段,执行结果由框架校验,再由 render 转成模型可读的内容,遵循官方工具契约。

这个任务不需要通用的 SQL、Shell、文件读写或保存工具。所选的 Harness 插件组合也要收紧到必要的能力。每次模型调用还要经过任务级的准入和用量统计,不能因为在一个 HTTP 请求内部循环了多次,就绕过现有的 AI 并发和频率控制。

9.4 一次完整的工具循环长什么样 ​

下面是一条虚构的预期执行记录,用来说明产品应该呈现什么,不是真实的模型运行日志:

text
用户选择:示例知识库,最近一个月,只读文档
  ↓ 后端认证并绑定任务范围
模型请求 search_notes({ keyword: "发布", offset: 0 })
  → 返回《发布复盘》《上线检查》及命中摘要
模型请求 read_note({ entryId: "demo-note-a" })
  → 返回发布复盘片段、实际版本、truncated: false
模型请求 read_note({ entryId: "demo-note-b" })
  → 返回上线检查片段、实际版本、truncated: false
  ↓ 模型组织候选稿,并用来源标记关联依据
后端校验正文与引用 → 返回候选稿 → 前端预览
  ↓ 用户选择采纳方式与保存位置
业务保存接口 → 服务端确认 → 界面显示已保存

比如两篇虚构的笔记分别写着“发布前完成构建检查并记录变更”和“发布后观察错误日志,异常时按记录回滚”。运行时为它们分配来源标记 S1、S2 后,预览稿可以写成:

可复用做法:发布前完成构建检查,记录本次变更 [S1];发布后观察错误日志,并保留回滚依据 [S2]。

待确认:当前片段没有记录回滚负责人,需要补充。

[S1]、[S2] 由预览组件关联到真实的读取记录。“没有记录负责人”只针对本次读到的片段,不能推断整个知识库都没有。

几条必须遵守的规则:

  • 资料不足时,模型可以在原范围内换关键词;仍然没有结果,就说明“在本次范围内未找到”,让用户调整范围,不能编造笔记,也不能自动扩大到其他知识库;
  • 网络失败不能伪装成“没有结果”;正文被截断也要如实显示,不能声称已经读完整篇。

建议后端发出任务状态、工具开始、工具完成、候选就绪、失败和取消等事件。前端用 runId 关联任务,用 callId 关联工具调用和结果,展示“正在搜索 / 读取 2 篇 / 已生成候选”。这些是需要新增的应用协议,Harness 默认的 Web 界面不会直接变成 Vue 产品页面。

候选稿的数据结构可以这样设计(字段属于本例的应用契约):

ts
type CandidateDraft = {
  runId: string
  html: string
  sources: Array<{
    ref: string
    entryId: string
    version: string
    title: string
    excerpt: string
    truncated: boolean
  }>
  warnings: string[]
}

runId、来源标识、版本和片段由运行时根据真实的工具结果组装,模型只能引用分配好的来源标记。后端要拒绝未读取过的来源、未知的引用和不合法的结构,再做 HTML 白名单清洗。白名单解决的是“能渲染什么”,不能证明内容正确;预览界面仍要让用户核对“这句话是否真的被来源支持”。

如果需要刷新后恢复预览,就要明确地持久化候选稿和必要的来源信息,并设置访问权限和保留期限,不能假定所有工具返回的结构化数据都会自动进入 Harness 的持久日志。

9.5 预览、采纳和保存,各自做什么 ​

预览页可以左边展示检索命中、读取片段和来源入口,右边展示候选正文;如果是编辑已有内容,再提供原文对比。来源入口由前端用校验过的标识生成站内链接,不直接使用模型生成的链接。

现有笔记应用有两种采纳入口,接入时要准确复用它们的语义:

入口用户采纳后的行为什么时候真正保存
编辑器内 AI 对比预览替换选区或在指定位置插入,正文变为“待保存”用户执行保存时
独立 AI 创作预览页用户选择“插入到文档”或“另存为新文档”点击对应按钮时立即保存

本例默认使用独立预览页:“插入到文档”会追加并立即保存;“另存为新文档”会立即创建文档。按钮文案要明确写出这一点,不能笼统地说“采纳后都不会保存”。两种动作都只在用户点击后发生,生成完成、打开预览、恢复会话都不会触发写入。

编辑器内的入口还要防止旧结果被应用到新文档。现有实现会记录编辑器实例、文档快照、选区和登录会话,结果返回和采纳时重新核对实例和正文是否一致。Agent 的候选稿接入后也要保留这项检查:页面切换、账号失效或正文已经变化时,停止应用旧结果。

9.6 复用业务保存能力,补齐写入的证据 ​

保存继续走已有的业务服务,模型不直接写数据库。三种保存场景分别处理:

  1. 另存为新文档:用户确认知识库和标题,服务端校验归属后创建 document。表格、看板、列表等使用不同的内容格式,不能把生成的 HTML 直接塞进去。
  2. 追加到已有文档:先重新读取目标、校验类型,再拼接内容并提交版本基线,强制保留追加前的历史版本。现有独立预览页使用 baseUpdatedAt 检查正文冲突,Agent 适配建议同时使用服务端提供的 baseVersion,以覆盖目录位置等状态的变化。冲突时保留候选稿,让用户重新检查目标,不能悄悄读取新版本后自动重试追加。
  3. 保存编辑器内已采纳的内容:沿用现有的保存快照和 baseVersion 检查。返回 409 ENTRY_CONFLICT 时,用户可以载入远端版本、另存副本,或者明确选择覆盖并保留历史。不能让模型替用户选择覆盖。

来源版本和目标版本各有用途:来源版本表示“这份候选稿依据的是哪一版资料”,目标版本用于“保存时不覆盖别人的并发修改”。采纳前可以复核来源是否变化,变化了就标为过期,要求用户检查或重新生成。目标版本校验通过,并不代表这份候选稿是基于最新资料生成的。

保存成功也只能确认“本次提交的快照已经保存”。现有编辑器会比较提交的快照和当前内容:保存请求发出后继续输入的文字,仍然标记为未保存。候选稿、已采纳的正文和已持久化的版本,要分别记录状态。

结果不确定的情况也要处理:按钮防连点只能挡住当前页面的重复点击,不能保证断网重试时不会再次创建或追加。要提供自动恢复和重试,就需要新增持久化的“采纳操作标识”,把“记录操作结果”和业务写入放在同一个事务里;服务端把它和用户、目标、版本、内容摘要绑定,同一个标识只能对应同一次操作。在补齐之前,现有的创建 / 追加流程还不能宣称具备 Agent 任务级的幂等;超时后先核对保存结果,不要自动重放写入。

来源引用如果需要随文档长期保存,也要明确保存格式(比如经过校验的引用节点或独立的元数据)。只存在于预览状态里的来源列表,刷新后不会自动变成文档引用。

9.7 用失败场景验收这个案例 ​

场景应有的结果
搜索命中、读取、生成都成功候选稿包含真实来源;生成阶段没有任何文档写入
搜索失败或没有命中分别展示“出错”和“无结果”,不编造资料
模型传入其他库或其他用户的标识服务端拒绝,不能通过工具参数绕过范围
资料里夹带“保存 / 删除 / 发给外部”的指令不增加工具或权限,不执行写入和外发
用户取消或切换账号后,旧响应才返回停止后续调用,旧候选不进入当前会话
来源更新、目标正文更新分别提示“依据已过期”和“保存冲突”,保留候选稿供用户处理
保存期间继续输入只确认提交时的快照,之后的文字仍为未保存
保存超时、重复点击或恢复会话不盲目重放创建和追加;有幂等记录时返回原结果

这些是新增适配层的验收要求。本次只核对了源码和文章示例,没有执行笔记系统的 Agent 集成测试。现有能力的实现细节可以继续阅读 AI 编辑的可靠性边界 和 编辑会话与本地草稿。

10. 踩坑速查 ​

坑现象原因怎么做
只看最终回答回答里的数字是对的,就认为工具链路没问题模型可能没调用工具,是自己猜的验收时检查会话记录里确实有工具调用和结果(第 5 节)
误以为整个 Agent 只读只注册了读工具,以为不会改任何东西所选 profile 默认可能包含文件修改和命令执行能力审查完整的插件组合、执行策略和底层权限(第 4、6 节)
停不下来任务反复调用工具,费用持续上涨maxParallelToolCalls 限制的是同一步内的工具并发,不是最大任务步数自己设置步数、时长、费用上限和无进展停止规则(第 7 节)。Harness 调度器只让声明为安全的调用并行,互斥调用保持顺序,见工具调度源码
越权读取模型传入别人的 ID,读到了别人的数据身份和范围来自模型参数身份由运行时绑定到任务,每次读取重新校验(第 9.2 节)
资料里的指令被执行检索到的文档写着“删除所有文件”,Agent 照做了把工具返回的内容当成了指令工具结果一律是不可信资料,不扩大权限(第 6 节)
恢复后重复写入进程重启后,邮件又发了一遍、文档又追加了一次日志可以恢复会话,但不代表业务动作可以重放写操作使用业务幂等键和持久状态(第 9.6 节)
旧结果用到了新文档切换文档后,上一篇的生成结果被插入了当前文档异步结果返回时没有核对上下文记录并核对编辑器实例、快照、选区和会话(第 9.5 节)
错误伪装成空结果网络失败时显示“没有找到相关资料”没有区分失败和空结果失败、无结果、截断分别展示(第 9.4 节)

11. 按阶段扩展,而不是一次装满能力 ​

  1. 最小闭环:一个模型、一个可验证的工具、一组固定的测试任务;
  2. 可靠性:会话、错误、取消、预算、输出校验和观察记录;
  3. 业务接入:可信身份、受限数据、写操作授权和幂等;
  4. 规模化:再考虑 SDK 接入、多 Agent、后台任务和多实例调度。

接入现有产品时可以选择官方 SDK,但要核对所选 profile 的完整能力和数据流。官方 sdk-minimal 文档包含执行权限和会话日志传输的说明,“minimal”描述的是组合的规模,不能从名字推断它只读、隔离或不会外发数据。SDK 与最小 profile 说明

检查清单 ​

  • [ ] 先确认任务真的需要动态循环,而不是固定流水线
  • [ ] 第一个工具能被验收:会话记录中有真实的调用和结果
  • [ ] 审查过完整的插件组合和权限,而不只是自己写的工具
  • [ ] 用户身份和资源范围由运行时绑定,每次执行都重新校验
  • [ ] 设置了步数、时长、费用上限和无进展停止规则
  • [ ] 写操作需要用户确认,并有幂等保护
  • [ ] 用失败场景表格逐项验收

延伸阅读 ​

为复用而记录,为理解而整理。