目录
深入解析 DeepSeek Harness:基于 Cordis 微内核的“一切皆插件”架构与深度实战
/      

深入解析 DeepSeek Harness:基于 Cordis 微内核的“一切皆插件”架构与深度实战

GeminiGeneratedImagec1vxpec1vxpec1vx.jpeg

深入解析 DeepSeek Harness:基于 Cordis 微内核的“一切皆插件”架构与深度实战

摘要:当大模型 Agent 走向生产环境,传统框架硬编码的 Prompt 拼装、黑盒执行循环和紧耦合工具调用往往让安全管控、上下文治理和多 Agent 协同难以落地。DeepSeek AI 开源的 DeepSeek Harness (dsh) 践行 “Everything is a plugin”(一切皆插件) 的设计哲学,基于微内核容器 Cordis 构建。本文将深度剖析其微内核原理、ctx 上下文传递机理、4 种事件分发模式的底层实现,并结合 极简可运行 Demo4 个生产级真实场景案例,带你彻底掌握 Harness 的生命周期扩展体系。


📑 目录


一、 为什么 Agent 底座需要“零特权微内核”?

传统 Agent 框架通常由一个庞大的运行时(Runtime)掌控一切,开发者只能在预留的极少数槽位写业务逻辑。而 DeepSeek Harness 认为:Agent 运行时的所有能力都应该是平等的、可替换的插件。

graph TD
    subgraph "Cordis 共享上下文容器 (ctx)"
        S1["ctx.llm (LLM 适配器服务)"]
        S2["ctx.tools (工具注册与执行管线)"]
        S3["ctx.sessions (不可变事件日志)"]
        S4["ctx.agents (Agent 调度与状态)"]
        S5["ctx.systemPrompt (Prompt 分段拼装)"]
    end

    C1["案例一: 企业安全与人工审批插件"] -->|拦截 tools/pre-execute| S2
    C2["案例二: 上下文双层防爆守卫插件"] -->|拦截 agent/pre-step 与 tools/post-execute| S4
    C3["案例三: 多 Agent 规范动态注入插件"] -->|监听 subagent/start| S4
    C4["案例四: 自定义私有 LLM 适配器插件"] -->|挂载 registerAdapter| S1

三大底层基石设计

  1. 零特权内核,一切皆服务(Everything is a Service):大模型接口、文件系统、工具注册中心、甚至 Agent Loop 循环,都是向 ctx 注入的标准 Cordis Service
  2. 模型可见即日志(Model-Visible ⟺ Logged):所有呈现给模型的输入,必须是 SessionEvent 日志的投影。保证任何会话均可确定性回放(Replay)与分支(Fork)。
  3. 能力三位一体(Capability Seam):接口定义(Service Definition)、具体实现(Service Provider,如本地/沙箱/远程)与面向模型的工具(Consumer)严格解耦。更换后端沙箱不需要改写任何 Tool 代码。

二、 Agent 运行周期:Turn 与 Step 生命周期全景

在深入事件机制前,必须厘清 Agent 的调度生命周期。在 dsh 中:

  • Step:单次模型推理 + 该次推理产生的一组工具调用。
  • Turn:从接收输入到最终没有待执行操作的完整闭环(包含 1 到多个 Step)。

image.png


三、 Cordis 是如何接入的?为什么每个地方都能拿到 ctx

1. 核心接入四步法

在 Harness 启动时(例如运行 pnpm dsh web,源码见 packages/boot/app-boot):

  1. 实例化根上下文const ctx = new Context(),初始化全局共享容器;
  2. 挂载加载器await ctx.plugin(Loader),载入配置解析引擎;
  3. 解析依赖拓扑:Loader 读取 cordis.yml / cordis.patch.yml,自动分析各插件的 inject 依赖声明并计算加载顺序;
  4. 生命周期注入:Cordis 依次调用各插件的 apply(ctx) 或构造函数,将当前上下文作为第一个参数显式注入给插件。

2. 为什么任何地方都能访问 ctx.<service>?(底层机制拆解)

sequenceDiagram
    participant Code as 你的插件代码
    participant Proxy as Context Proxy 代理层
    participant Registry as Cordis 依赖注册表

    Code->>Proxy: 读取 ctx.tools
    Note over Proxy: 1. 触发 get 拦截器<br/>2. 校验是否声明了 inject: ['tools']
    alt 未声明 inject
        Proxy-->>Code: 抛出错误: cannot get property tools without inject
    else 已声明 inject
        Proxy->>Registry: 从容器获取已就绪的 ToolsService 实例
        Registry-->>Proxy: 返回 ToolsService 实例
        Proxy-->>Code: 成功交付 ctx.tools
    end
  • 参数注入(Parameter Injection):插件导出 apply(ctx) 时,Cordis 框架将当前上下文实例作为入参传入,借助 JS 闭包,文件内的所有函数均可访问该 ctx
  • 服务挂载(Service Providing):当某个插件继承 Service 类执行 super(ctx, 'tools') 时,会自动向上下文注册名为 tools 的服务。
  • ES6 Proxy 深度代理Context 对象本质上是一个 Proxy。当你读取 ctx.tools 时,Proxy 会自动校验当前插件是否通过 inject: ['tools'] 声明了依赖,确保依赖拓扑安全,避免未就绪时的空指针异常。
  • 原型链派生(Context Fork & Extend):当为特定 Agent 派生隔离会话时,框架调用 ctx.extend(),子上下文通过原型链继承父级所有全局服务,同时支持局部隔离(ctx.isolate())。

3. 极简 5 分钟上手 Demo(体验 Cordis 接入全流程)

下面是一个完全自包含的 TypeScript 极简 Demo,演示如何通过 Cordis 创建容器、提供服务、声明依赖与事件通信:

import { Context, Service } from '@deepseek-ai/cordis'

// -------------------------------------------------------------
// 1. 定义一个服务插件:计算服务 (提供 ctx.calc)
// -------------------------------------------------------------
declare module '@deepseek-ai/cordis' {
  interface Context {
    calc: CalculatorService // TypeScript 类型扩展声明
  }
}

class CalculatorService extends Service {
  constructor(ctx: Context) {
    // 关键:注册服务名为 'calc',全局即可通过 ctx.calc 访问
    super(ctx, 'calc')
  }

  add(a: number, b: number) {
    return a + b
  }
}

// -------------------------------------------------------------
// 2. 定义一个业务插件:使用计算服务并监听事件
// -------------------------------------------------------------
const MathConsumerPlugin = {
  name: 'math-consumer',
  inject: ['calc'], // 【核心】:声明依赖 calc 服务,Cordis 保证加载顺序
  apply(ctx: Context) {
    // 通过 ctx 使用 calc 服务
    const sum = ctx.calc.add(10, 20)
    console.log(`[MathConsumerPlugin] 10 + 20 计算结果 = ${sum}`)

    // 监听全局自定义事件
    ctx.on('custom/alert', (msg: string) => {
      console.log(`[MathConsumerPlugin] 收到广播通知: ${msg}`)
    })
  }
}

// -------------------------------------------------------------
// 3. 启动应用:创建 Context 并挂载插件
// -------------------------------------------------------------
async function bootstrap() {
  const ctx = new Context() // 创建根上下文

  // 挂载计算服务插件
  await ctx.plugin(CalculatorService)

  // 挂载消费插件 (Cordis 发现其依赖 calc 已就绪,立即激活该插件)
  await ctx.plugin(MathConsumerPlugin)

  // 广播一个测试事件
  ctx.emit('custom/alert', 'DeepSeek Harness 启动就绪!')
}

bootstrap()

四、 4 种事件分发模式深度拆解(Waterfall, Emit, Serial, Parallel)

Cordis 提供了 4 种事件分发模式(参见 docs/event-producer-consumer.md)。

1. 4 种模式的共性与差异:全员必达 vs 条件拦截

核心共性:它们都基于统一的 ctx.on(eventName, ...) 机制进行注册,在默认无干预状态下,所有插件均能接收到事件广播

关键差异在于执行过程中是否允许插件行使**“短路/拦截权”**:

类别模式执行机制与短路特性现实比喻
全员必达类emitparallel100% 必定收到。没有任何插件能够阻止后续插件接收事件。广播电台 / 全员大会(所有人都能听到)
条件拦截类waterfallserial默认按序流转,但允许上游插件提前终止/短路。上游一旦做出最终决策,下游插件将被跳过。多道安检关卡 / 审核接力赛(前一关拦截后一关即看不到)

2. waterfall 模式:洋葱环绕拦截(Around-Middleware)

机制与“U 型回旋流”

所有监听器像洋葱一样层层包裹,最后一个参数是 next 函数。

  • 监听器执行前置逻辑 $\rightarrow$ 调用 await next() 委托给下游 $\rightarrow$ 下游执行完毕后,控制权回旋回到 await next() 的下一行执行后置逻辑。
  • 如果不调用 next(),整条链路立即短路(Short-circuit),内层和核心业务直接被跳过。

image.png

真实代码案例(tools/pre-execute 权限拦截与耗时打点)

ctx.on('tools/pre-execute', async (exec, next) => {
  const start = Date.now()

  // 1. [前置阶段]: 检查是否包含危险命令
  if (exec.name === 'bash' && (exec.args as any)?.command?.includes('rm -rf')) {
    // 【短路拦截】:故意不调用 next(),直接返回拒绝,内层和实际工具都不会执行
    return { kind: 'deny', reason: '高危删除命令已被拦截' }
  }

  // 2. [深入内层]: 调用 next() 放行
  const decision = await next()

  // 3. [后置阶段]: 下游全部跑完回到这里,计算总耗时并记录
  console.log(`工具 ${exec.name} 前置检查总耗时: ${Date.now() - start}ms`)
  return decision
})

3. emit 模式:单向广播(Fire-and-Forget)

机制与特点

  • 同步触发,不等待 Promise:按注册顺序依次调用每个监听器,emit 绝不会 await 其返回的 Promise。
  • 错误隔离:单个监听器报错会被独立捕获并记录日志,绝不会阻断其他监听器或主流程
graph LR
    Dispatcher["ctx.emit('session/event', ...)"] --> L1["监听器 1: Web UI 推送"]
    Dispatcher --> L2["监听器 2: 审计日志记录"]
    Dispatcher --> L3["监听器 3: 监控指标上报"]
    Note["触发即走,不阻塞主流程,无需返回值"]

真实代码案例(session/event 实时推送 WebSocket)

ctx.on('session/event', (session, event) => {
  // 1. 捕获模型流式可见文本增量 (Text Delta)
  if (event.type === 'assistant/chunk' && event.data.chunk.type === 'text-delta') {
    websocket.send(JSON.stringify({ type: 'text', content: event.data.chunk.text }))
  }
  // 2. 捕获模型思考增量 (Reasoning Delta)
  if (event.type === 'assistant/chunk' && event.data.chunk.type === 'reasoning-delta') {
    websocket.send(JSON.stringify({ type: 'thinking', content: event.data.chunk.text }))
  }
})

4. serial 模式:串行有序判定与 Bail 提前终止

机制与源码级实现

serial 是按注册顺序依次 await 执行,其在 Cordis 中的底层源码如下:

// vendor/cordis/src/events.ts
async serial(...args: any[]) {
  for (const cb of this.dispatch('serial', args)) {
    const result = await cb(...args)
    // 关键点:如果某个插件返回了非 undefined/null 的有效值,立即 return 并终止循环!
    if (isBailed(result)) return result
  }
}
  • 严格串行等待:插件 1 必须完全执行完(await cb()),才会进入插件 2。
  • Bail(提前熔断)机制:只要某个插件返回了有效决策(非 undefined/null),循环立即终止并返回该结果,后续所有插件直接被跳过

image.png


5. parallel 模式:全并发屏障(Concurrent Barrier)

机制与特点

  • 全员并发:所有监听器通过 Promise.allSettled 同时启动,最大化利用 I/O 吞吐。
  • 严格屏障(Barrier)保证:主流程挂起等待,直到所有监听器全部 Resolve 或 Reject 后才允许继续。
  • 多错误聚合(AggregateError:如果有多个插件写入报错,错误会统一收集抛出,不会被吞噬。
sequenceDiagram
    autonumber
    participant Core as 框架内核
    participant Store1 as 存储插件 1 (SQLite 写入)
    participant Store2 as 存储插件 2 (JSONL 追加)
    participant Exporter as 监控插件 3 (OpenTelemetry 遥测)

    Core->>Store1: 并发触发 (Promise)
    Core->>Store2: 并发触发 (Promise)
    Core->>Exporter: 并发触发 (Promise)
  
    Note over Store1,Exporter: 三者全并发并行执行 I/O 操作...
  
    Store1-->>Core: 写入完成 (Resolved)
    Store2-->>Core: 写入完成 (Resolved)
    Exporter-->>Core: 发送完成 (Resolved)
  
    Note over Core: 屏障解除:确认所有数据安全落盘,继续下一步!

6. waterfallserial 的终止机制深度对比

二者虽然都是按注册顺序(FIFO)执行都能终止后续插件,但设计哲学与终止方式不同:

graph TD
    subgraph "Waterfall 洋葱模式"
        W1["插件 1 (调用 next)"] --> W2["插件 2 (不调用 next)"]
        W2 -.->|洋葱链断开| W3["插件 3 (被跳过)"]
    end

    subgraph "Serial 串行模式"
        S1["插件 1 (return undefined)"] --> S2["插件 2 (return 结果对象)"]
        S2 -.->|触发 Bail 熔断| S3["插件 3 (被跳过)"]
    end
对比维度waterfall(洋葱模式)serial(串行模式)
执行顺序注册顺序(外层$\rightarrow$ 内层 $\rightarrow$ 外层)注册顺序(插件 1$\rightarrow$ 插件 2 $\rightarrow$ ...)
如何放行后续插件?必须显式调用并返回 await next()返回 undefined 或不写 return
如何终止后续插件?故意不调用 next(),直接 return 结果返回一个非空有效值(触发 Bail 判定)
执行流向U 型回旋(先由外向内,再由内向外)单向直线(一个接一个跑完即止)
核心优势既管“前置拦截”,又管“后置改写/统计/finally清理”逻辑扁平清晰,适合单向逐项检查与裁决

五、 全景扩展点速查矩阵

领域扩展点名称模式核心作用与典型应用场景
工具管线tools/pre-executewaterfall执行前安全校验:识别高危命令短路阻断,或挂起触发人工二次审批。
工具管线tools/executewaterfall执行生命周期包裹:执行超时熔断(Timeout)、网络重试与执行打点。
工具管线tools/post-executewaterfall输出裁剪与清洗:单次超长工具结果裁剪(Spill Policy)、敏感词脱敏。
工具管线tools/resultemit审计与指标:记录不可变最终执行结果与耗时。
执行循环agent/pre-stepwaterfall单步模型调用前介入:动态注入环境状态、评估全局 Token 并触发自动压缩。
执行循环agent/requestwaterfall底层请求包装:在真正发起 LLM 网络通信前的最后一道包装层。
执行循环agent/request-errorwaterfall异常熔断与降级:模型调用报错(如 429 限流、上下文超限)时自动重试或降级。
执行循环agent/turn-stoppingserial停机验收与强制续期:模型认为任务完成准备停止时触发,未达验收标准可强制追加 Step。
多智能体subagent/startemit子 Agent 启动拦截:获取子 Agent 实例,注入当前任务专项约束规范。
多智能体subagent/endemit子 Agent 执行结束,收集指标并通知父 Agent。
文件沙箱fs/write-intentwaterfall写前拦截:防止模型未经读取盲目覆盖已有文件。
文件沙箱fs/observedemit文件读写成功事实通知,触发代码索引重构。
会话数据session/eventemit全量事件数据流:监听用户输入、模型 Token 流与工具结果,推送到 UI/Bot。
会话数据session/flushparallel会话持久化刷盘,对接外部企业数据库。

六、 深度实战:四大企业级案例全景落地


案例一:企业级高危命令拦截与人工二次审批(Human-in-the-Loop)

📌 业务痛点

当大模型拥有 bash 执行权限时,可能意外生成 rm -rf /git push --forceDROP TABLE 等破坏性命令。我们希望:

  1. 普通只读命令自动放行;
  2. 检测到危险命令时,暂停当前执行流程,在前端 UI 弹出确认对话框;
  3. 人工点击“允许”后继续,点击“拒绝”则返回拒绝原因给模型以触发自我修正;
  4. 全程记录不可变审计日志。

🛠️ 涉及扩展点

  • tools/pre-executewaterfall 洋葱拦截)
  • ctx.approval.request()(人工审批服务)
  • tools/resultemit 审计广播)

💻 完整代码实现

import type { Context } from '@deepseek-ai/cordis'
import type { PreToolDecision, ToolExecution } from '@deepseek-ai/dsh-tools'

export const name = 'enterprise-security-guard'
export const inject = ['tools', 'approval'] // 声明依赖工具服务与审批服务

const DANGEROUS_COMMAND_PATTERNS = [
  /rm\s+-rf/i,
  /git\s+push\s+.*--force/i,
  /drop\s+database|drop\s+table/i,
  /mkfs/i,
  /chmod\s+-R\s+777/i,
]

export function apply(ctx: Context) {
  // 1. 在工具执行前进行洋葱拦截
  ctx.on('tools/pre-execute', async (exec: ToolExecution, next): Promise<PreToolDecision> => {
    // 仅拦截 bash 工具调用
    if (exec.name === 'bash') {
      const command = String((exec.args as { command?: string })?.command || '')
      const isDangerous = DANGEROUS_COMMAND_PATTERNS.some(pattern => pattern.test(command))

      if (isDangerous) {
        ctx.logger.warn(`[Security] 检测到高危命令: ${command},挂起并请求人工审批...`)

        // 唤起人机协同审批流程 (挂起当前异步执行)
        const outcome = await ctx.approval.request({
          agent: exec.agent,
          toolName: exec.name,
          reason: `模型试图执行高危命令: \`${command}\`,需要管理员确认。`,
          signal: exec.signal,
        })

        // 用户在 UI 上点击了拒绝
        if (outcome.decision === 'deny') {
          return {
            kind: 'deny',
            reason: `操作已被人工管理员拒绝!审批附言: ${outcome.reason || '无'}。请调整你的方案,不要使用破坏性指令。`,
          }
        }

        ctx.logger.info(`[Security] 管理员已批准高危命令: ${command}`)
      }
    }

    // 2. 校验通过,放行进入下一层
    return next()
  })

  // 3. 监听不可变的执行结果,记录审计日志
  ctx.on('tools/result', (event) => {
    if (event.name === 'bash') {
      ctx.logger.info(`[Audit] Bash 执行结束 | 状态: ${event.status} | 耗时: ${event.durationMs}ms`)
    }
  })
}

案例二:智能上下文“双层防爆”与自动压缩(Compaction Guard)

📌 业务痛点

在复杂长任务中,单次工具调用可能输出数万行构建日志,或多轮对话导致 Token 迅速逼近模型上下文上限(如 128k),导致后续 LLM 请求直接报错 400(Context Window Exceeded)。我们构建微观 + 宏观双层防御

  1. 微观层:单次工具输出过大时,自动裁剪并转储文件;
  2. 宏观层:模型单步请求前评估全局 Token 压力,超标时自动触发历史轮次语义压缩。

🛠️ 涉及扩展点

  • tools/post-executewaterfall 工具输出过滤)
  • agent/pre-stepwaterfall 单步前全局 Token 预算校验)
  • agent/request-errorwaterfall 异常降级恢复)

💻 完整代码实现

import type { Context } from '@deepseek-ai/cordis'
import type { PreStepDecision, Agent } from '@deepseek-ai/dsh-agent'
import type { ToolResult } from '@deepseek-ai/dsh-tools'

export const name = 'context-window-guard'
export const inject = ['tools', 'agents', 'tokenMeter', 'compaction']

const MAX_TOOL_OUTPUT_CHARS = 8000 // 单次工具输出上限
const TOKEN_PRESSURE_RATIO = 0.8   // 达到模型窗口 80% 时触发压缩

export function apply(ctx: Context) {
  // 1. 微观层:在工具返回后截断巨量输出 (tools/post-execute)
  ctx.on('tools/post-execute', async (exec, result: ToolResult, next) => {
    const rawResult = await next()
    if (typeof rawResult.output === 'string' && rawResult.output.length > MAX_TOOL_OUTPUT_CHARS) {
      const head = rawResult.output.slice(0, 3000)
      const tail = rawResult.output.slice(-3000)
      const omitted = rawResult.output.length - 6000

      // 改写工具输出,替换为带摘要的安全文本
      return {
        ...rawResult,
        output: `${head}\n\n... [⚠️ 框架自动截断: 中间省略 ${omitted} 字符,防止 Context 溢出] ...\n\n${tail}`,
      }
    }
    return rawResult
  })

  // 2. 宏观层:每次向 LLM 发送请求前评估 Token 压力 (agent/pre-step)
  ctx.on('agent/pre-step', async (agent: Agent, next): Promise<PreStepDecision> => {
    const measurement = ctx.tokenMeter.measure(agent.session)
    const contextWindow = 128000 // 假设当前模型窗口 128k

    if (measurement.totalTokens > contextWindow * TOKEN_PRESSURE_RATIO) {
      ctx.logger.warn(`[Context Guard] Token 压力预警 (${measurement.totalTokens} tokens),自动触发上下文压缩...`)
      // 调用底层压缩引擎对历史会话进行语义总结
      await ctx.compaction.compactNow(agent, new AbortController().signal)
    }

    return next() // 放行执行模型调用
  })

  // 3. 兜底层:当模型仍然返回上下文溢出错误时进行紧急恢复
  ctx.on('agent/request-error', async (error, agent, next) => {
    if ((error as any)?.code === 'CONTEXT_WINDOW_EXCEEDED') {
      ctx.logger.error(`[Context Guard] 捕获到上下文溢出错误,执行紧急强制修剪...`)
      await ctx.compaction.compactNow(agent, new AbortController().signal)
      return { retry: true } // 指示 Agent Loop 立即重试
    }
    return next()
  })
}

案例三:多智能体协作与规范动态注入(Subagent Dynamic Rule Injection)

📌 业务痛点

主 Agent 在排查复杂 Bug 时,通过 subagent 工具派发了一个子 Agent 专门去分析某个目录的代码。
因为子 Agent 拥有完全隔离的对话上下文,它往往不知道主项目的特定规范(例如:“必须使用 Vitest 而非 Jest”、“严禁修改已有类型定义”)。我们希望在子 Agent 启动瞬间自动向其注入专属上下文

🛠️ 涉及扩展点

  • subagent/startemit 广播事件)
  • child.inject()(上下文无损注入)
  • subagent/endemit 追踪配对)

💻 完整代码实现

import type { Context } from '@deepseek-ai/cordis'
import type { SubagentRunInfo, SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent'

export const name = 'subagent-governance'
export const inject = ['agents', 'subagents']

export function apply(ctx: Context) {
  // 1. 监听子 Agent 启动事件
  ctx.on('subagent/start', (info: SubagentRunInfo) => {
    // 根据子 Agent 的 Session ID 获取其实时运行实例
    const childAgent = ctx.agents.get(info.id)
    if (!childAgent) return

    const cwd = childAgent.session.header.cwd
    ctx.logger.info(`[Subagent] 捕获子 Agent 启动 | RunId: ${info.runId} | 路径: ${cwd}`)

    // 【核心操作】:动态向子 Agent 注入专项任务规则
    // 这段内容会出现在子 Agent 的下一次模型推理上下文中,且不会污染父会话
    childAgent.inject({
      content: [{
        type: 'text',
        text: [
          '【子智能体执行约束规范】',
          '1. 你是由主智能体派发的只读代码调研任务。',
          '2. 请优先使用 `read` 和 `grep` 工具收集事实。',
          '3. 完成分析后,请直接给出精确的结论与关键代码行号,不要尝试调用 `write` 或 `edit` 修改文件。',
        ].join('\n'),
      }],
      source: { kind: 'plugin', name: 'subagent-governance' },
    })
  })

  // 2. 监听子 Agent 结束事件,统计耗时并上报
  ctx.on('subagent/end', (endInfo: SubagentRunEndInfo) => {
    ctx.logger.info(`[Subagent] 子 Agent 完成任务 | RunId: ${endInfo.runId} | 提供方: ${endInfo.provider}`)
  })
}

案例四:零侵入扩展私有大模型适配器(Custom Private LLM Adapter)

📌 业务痛点

企业内部部署了基于 vLLM / Ollama 的私有开源大模型,或有自建的统一 API 聚合网关,希望将 Harness 的模型后端无缝切换到私有模型,同时完整支持流式文本、思考链(Reasoning)和工具调用(Tool Calling)

🛠️ 涉及扩展点

  • 继承 LlmAdapter
  • 挂载到 ctx.llm.registerAdapter(['my-custom-provider'], adapter)

💻 完整代码实现

import { Context } from '@deepseek-ai/cordis'
import { LlmAdapter, type GenerateOptions, type StreamChunk, HarnessError } from '@deepseek-ai/dsh-llm'

export const name = 'llm-private-gateway'
export const inject = ['llm']

class PrivateGatewayAdapter extends LlmAdapter {
  constructor(private baseUrl: string, private apiKey: string) {
    super()
  }

  // 核心:实现标准流式生成器契约
  override async *stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
    const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`,
      },
      body: JSON.stringify({
        model: options.model,
        messages: options.messages,
        stream: true,
      }),
      signal: options.signal, // 响应 Harness 的主动中断信号
    })

    if (!response.ok) {
      throw new HarnessError(`网关请求失败: ${response.statusText}`, 'HTTP_ERROR')
    }

    const reader = response.body?.getReader()
    if (!reader) return

    const decoder = new TextDecoder()
    let buffer = ''

    while (true) {
      const { done, value } = await reader.read()
      if (done) break

      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() || ''

      for (const line of lines) {
        const trimmed = line.trim()
        if (!trimmed.startsWith('data: ') || trimmed === 'data: [DONE]') continue

        const payload = JSON.parse(trimmed.slice(6))
        const delta = payload.choices?.[0]?.delta

        // 1. 产生可见文本增量
        if (delta?.content) {
          yield {
            type: 'content-delta',
            index: 0,
            delta: { type: 'text-delta', text: delta.content },
          }
        }

        // 2. 产生思考过程增量 (Reasoning Content)
        if (delta?.reasoning_content) {
          yield {
            type: 'content-delta',
            index: 1,
            delta: { type: 'reasoning-delta', text: delta.reasoning_content },
          }
        }
      }
    }

    // 3. 严格按照协议在结束时发出 finish 块
    yield {
      type: 'finish',
      reason: 'stop',
      usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
    }
  }
}

export function apply(ctx: Context) {
  const adapter = new PrivateGatewayAdapter('https://api.internal.mycompany.com', process.env.MY_GATEWAY_KEY || '')
  // 注册为名为 private-llm 的适配器,所有使用该 provider 的模型请求都会路由至此
  ctx.llm.registerAdapter(['private-llm'], adapter)
}

七、 最佳实践与避坑指南(Gotchas & Best Practices)

在基于 Cordis 开发 Harness 插件时,有几个常见的工程陷阱需要特别规避:

  1. 务必显式声明 inject 依赖
    • ❌ 错误:在插件里直接写 ctx.tools.register(...),但没有声明 export const inject = ['tools']
    • ⚠️ 后果:Cordis 的 Proxy 拦截器会立刻抛出 cannot get property "tools" without inject
  2. waterfall 监听器切勿遗漏 await next()
    • ❌ 错误:在 tools/pre-execute 中做完检查后直接 return 空对象,忘记写 return next()
    • ⚠️ 后果:整条洋葱链被意外短路,后续所有插件和实际 Tool 执行被无意阻断。
  3. emit 事件中避免执行耗时阻塞 I/O
    • ❌ 错误:在 session/event 监听器中执行耗时几百毫秒的同步加密或大文件读写。
    • ⚠️ 建议:emit 是高频触发的(如流式 Token),复杂耗时任务应转入异步 Worker 或任务队列中消费。
  4. 恪守“模型可见即日志(Model-Visible ⟺ Logged)”原则
    • ❌ 错误:将关键状态保存在插件的全局变量内存中,不写入 Session Log。
    • ⚠️ 后果:会导致会话在重启、分支(Fork)或离线回放(Replay)时丢失状态,破坏确定性。所有给模型看的数据,均应通过 agent.inject() 产生不可变的 SessionEvent

八、 总结与架构启示

image.png

场景传统 Agent 框架的做法DeepSeek Harness 的做法
添加新工具修改核心代码或继承特定 BaseTool 类ctx.tools.register(defineTool(...)),自动生成 Schema 与卡片渲染
增加权限拦截在 Tool 的 execute 方法里硬编码 if-else挂载 tools/pre-execute 洋葱中间件,业务逻辑与安全策略解耦
接入新大模型改造底层请求客户端继承 LlmAdapter 并调用 ctx.llm.registerAdapter
多 Agent 治理依赖框架特定的 Agent 群聊协议监听 subagent/start,通过 child.inject() 动态下发规则

DeepSeek Harness 通过 Cordis 微内核将执行状态、事件分发与功能实现彻底解耦。每一个插件都能通过标准扩展点对 Agent 的行为进行观察、拦截和改写。这种“一切皆插件”的设计,为构建高可控、企业级的自主智能体提供了极具参考价值的架构范式。


九、 官方源码与权威参考链接

作为一篇独立的深度架构解析,以下为 DeepSeek Harness 官方代码仓库与核心架构设计白皮书的权威链接:


标题:深入解析 DeepSeek Harness:基于 Cordis 微内核的“一切皆插件”架构与深度实战
作者:gitsilence
地址:https://blog.lacknb.cn/articles/2026/08/16/1786867929449.html