DeepSeek Harness 架构梳理 · Java 后端视角

仓库:deepseek-ai/deepseek-harness(本地:~/git/deepseek-harness) 一句话:"一切皆插件"的 AI Agent 框架,用 TypeScript 把 Spring 那套模块化 + 依赖注入 + 事件驱动的思想,搬到了 AI Agent 领域。


1. 先建立整体认知

对 Java 后端来说,最快理解方式是这套类比:

DeepSeek Harness (DSH) Java / Spring 类比
Cordis Context(ctx ApplicationContext(服务注册表 + 事件总线)
Plugin(apply(ctx, config) @Configuration + 一个 Spring Boot Starter
ctx.effect() / ctx.on() @EventListener + 生命周期自动清理
Service Definition Java interface(能力接口)
Service Provider 接口的实现类(@Service
Consumer 注入并使用接口的代码
Capability Seam SPI(ServiceLoader 机制)
ctx.tools.register() 注册一个 Bean 到工具注册表
Session log Event Sourcing 的 append-only 事件日志
Bundle / Profile Spring Boot Starter / 启动配置组合
Agent loop 一个 while 循环(ReAct 模式)

核心思想:整个产品——模型适配器、工具注册表、会话日志、甚至 agent 循环本身——全部都是插件,没有"特权核心"。你想改任何一部分,都是"挂一个插件"而不是"改框架源码"。


2. 技术栈与工程结构

packages/
├── core/           # 产品 API 脊柱
│   ├── session/        # append-only 会话事件日志
│   ├── system-prompt/  # prompt 段 + 工具 schema 组装
│   ├── tools/          # 工具注册表 + 受保护的执行管道
│   ├── agent/          # Agent 接口 + 实时注册表
│   └── agent-loop/     # 默认驱动(实现 Agent 接口,就是那个 while 循环)
├── llm/            # LLM 能力:Service Definition + DeepSeek 适配器
├── shell/          # bash 能力:服务定义 + 本地/pwsh 提供者 + 工具 Consumer
├── fs/             # 文件系统能力 + 策略
├── web/            # web 搜索/抓取能力
├── subagent/       # 子代理能力
├── workflow/ todo/ plan/ compaction/ ... # 其他能力
└── bundle/         # 可分发的 profile 补丁层

3. Agent 循环(最核心,Java 开发者重点看这个)

代码在 packages/core/agent-loop/src/agent.ts,类名 ReactLoopAgent

用伪代码还原它的逻辑,就是:

// 类比 Java 写法
void kick() {
    while (turn()) {}   // 一直跑,直到没活干
}

boolean turn() {
    appendEvent("turn/start");
    while (true) {
        // ① 组装 prompt + 工具 schema,从会话日志推导历史
        var assembly = systemPrompt.assemble(...);

        // ② 调模型(流式)
        var message = llm.stream(request);   // assistant/message

        // ③ 解析出工具调用
        var toolCalls = message.toolCalls();
        if (toolCalls.isEmpty()) {
            return true;   // 没有工具调用 = 这一轮结束
        }

        // ④ 执行工具,结果回填
        executeToolCalls(toolCalls);

        // ⑤ 回到 while 循环,带着工具结果再次调模型
    }
    appendEvent("turn/end");
}

这就是经典的 ReAct(Reason + Act)循环:调模型 → 模型说要调工具 → 执行工具 → 结果回填 → 再调模型 → 直到模型不再调工具。

关键点(Java 开发者容易忽略的): - 一个 step = 一次模型请求 + 它触发的所有工具调用;一个 turn = 0 或多个 step - 模型能看到的所有上下文,都必须来自会话日志(Session log)——"Model-visible means logged"。这相当于:模型的历史不是存在内存变量里,而是从 append-only 的事件日志里"重放/投影"出来的。这个设计让 fork、resume、回放、telemetry 全都基于同一条事件流。


4. 插件机制:一切皆 apply

每个插件就是一个导出的 apply(ctx, config) 函数,没有魔法。以 bash 工具为例(packages/shell/tool-bash/src/index.ts):

export function apply(ctx: Context, config: Config = {}): void {
    // ① 往 prompt 里加一段说明
    ctx.systemPrompt.section({ name: 'tool:bash', order: 105, text: '...' });

    // ② 注册一个工具(这是核心)
    ctx.tools.register(defineTool({
        name: 'bash',
        description: 'Execute a bash command...',   // 给模型看的描述
        parameters: { command: {...}, timeoutMs: {...} },  // JSON Schema
        async execute(args, exec) {
            const result = await ctx.shell.run(...);   // 调底层 shell 能力
            return { kind: 'foreground', ...result };
        },
    }));
}

对比 Javaapply() ≈ 一个 @Configuration 类,defineTool() ≈ 定义一个 @Bean 并注册,ctx.tools.register() ≈ 把这个 Bean 放进工具注册表(模型通过 JSON Schema 知道有哪些工具可用)。


5. Capability Seam(最优雅的扩展点设计)

这是 DSH 最值得学的一点,叫做 Seam(接缝),由三个角色组成:

Service Definition(接口)  →  Service Provider(实现)  →  Consumer(使用方,通常是面向模型的工具)

例子:shell 能力 - Service Definitionctx.shell 的接口(runstartresolve 等) - Service Provider:本地 bash 实现 / pwsh 实现 / 远程沙箱实现 - Consumertool-bash 这个工具(调用 ctx.shell.run()

威力:换一个 Provider,整个产品就变了。文件系统和子进程共享同一个"执行世界",把它们指向远程沙箱,Bash、PTY、LSP 一起跟着变,不用 fork 任何 Provider

对比 Java:这就是标准 SPI 的升级版——接口、实现、使用方严格分离,且实现可被配置动态替换(类比 @ConditionalOnMissingBean + 多种实现按 profile 切换)。


6. 事件系统(三域)

事件域 作用 Java 类比
Session events 持久事实,写入日志(turn/startuser/messageassistant/messagetool/result Event Sourcing 的事件
Agent events 进行中的工作(agent/pre-stepagent/request 应用内事件
Capability events 挂策略和适配器(fs/*tools/* 领域事件

其中 agent/pre-stepagent/requestllm/streamtools/*waterfall(瀑布):监听者必须调 next() 才能放行(类比责任链/拦截器)。agent/turn-stopping 是串行的,没有 next()

对比 Javactx.effect()/ctx.on() ≈ 事件监听,waterfall ≈ HandlerInterceptor 责任链。


7. 给 Java 开发者的学习路径建议

按这个顺序看,能最快上手:

  1. 先读 docs/architecture.md(本仓库,含 turn flow 的时序)——建立全局图
  2. 再看 docs/cordis-primer.md——理解 Cordis 的 ctx.effect() / ctx.on() / Service 机制(类比 Spring IoC 入门)
  3. 精读 packages/core/agent-loop/src/agent.ts——这是核心,就是那个 while 循环
  4. 看一个工具实现packages/shell/tool-bash/src/index.ts)——理解"怎么加一个能力"
  5. 看一个 Seam 的三件套shell 包的 Definition/Provider/Consumer)——理解"怎么替换一个能力"

看完这 5 个,你对"AI Agent 框架怎么设计"就有了完整的工程认知,剩下的 30 个包都是同构的重复。


8. 关键收获(带回 Java 世界的启发)

  1. "一切皆插件"让 agent 框架无限可扩展:不像 Claude Code 那样功能写死在框架里,DSH 里连"agent 循环"都能被替换。
  2. Session log 是唯一的真相源:模型上下文从事件日志投影而来,而不是靠内存状态。这解决了 AI 应用的"可恢复、可回放、可 fork"难题——Java 世界里做状态机/事件溯源的同学会很熟悉。
  3. Seam 三件套是扩展性设计的范本:接口、实现、使用方彻底解耦,Provider 可热替换。
  4. 工具 = JSON Schema + execute 函数:模型通过 schema 认识工具,通过 execute 调用工具,中间是受保护的执行管道(sandbox、approval、timeout 都挂在管道上)。

本梳理基于仓库 deepseek-ai/deepseek-harness 当前代码(README、AGENTS.md、docs/architecture.md、core/agent-loop/src/agent.ts、shell/tool-bash/src/index.ts 精读)。开发者预览阶段,结构仍在快速迭代。