DeepSeek Harness 源码教程
DEEPSEEK HARNESS · OFFLINE TUTORIAL

11 · mini-DSH 实战

从零重新推导 Harness

0. 目标

不要复制 DSH;自己重新推导它。只做 Context、EventBus、Session、LLM Runtime、Tool Runtime、Agent Loop,然后逐层补足。

1. Context

class Context {
  services = new Map<string, unknown>()

  provide<T>(name: string, value: T) {
    this.services.set(name, value)
  }

  get<T>(name: string): T {
    return this.services.get(name) as T
  }
}

第一课:Consumer 不 import Provider。

2. Waterfall

async function waterfall(handlers, initial) {
  async function dispatch(index, value) {
    const handler = handlers[index]
    if (!handler) return value

    return handler(value, nextValue =>
      dispatch(index + 1, nextValue)
    )
  }
  return dispatch(0, initial)
}

3. Session

type SessionEvent =
  | { type: "turn/start" }
  | { type: "step/start" }
  | { type: "user/message"; content: string }
  | { type: "assistant/message"; content: string }
  | { type: "tool/call"; id: string; name: string; args: unknown }
  | { type: "tool/result"; id: string; result: unknown }
  | { type: "step/end" }
  | { type: "turn/end" }

class Session {
  events: SessionEvent[] = []
  append(event: SessionEvent) { this.events.push(event) }
  deriveMessages() { /* projection */ }
}

Facts first. Projection second.

4. LLM Seam

interface LlmAdapter {
  stream(request: ModelRequest): AsyncIterable<ModelChunk>
}

class LlmRuntime {
  adapters = new Map<string, LlmAdapter>()
  register(provider, adapter) { this.adapters.set(provider, adapter) }
  stream(provider, request) {
    const adapter = this.adapters.get(provider)
    if (!adapter) throw new Error("Unknown provider")
    return adapter.stream(request)
  }
}

5. Tool Runtime

interface Tool {
  name: string
  executionMode: "parallel" | "exclusive"
  execute(args: unknown): Promise<unknown>
}

下一步不要直接调用 tool.execute,而是实现 pre → guard → execute waterfall → post → finalize。

6. 最小 Agent Loop

async function runTurn(input: string) {
  session.append({ type: "turn/start" })
  let nextInput = input

  while (nextInput) {
    session.append({ type: "step/start" })
    session.append({ type: "user/message", content: nextInput })

    const messages = session.deriveMessages()
    const response = await callModel(messages)

    session.append({
      type: "assistant/message",
      content: response.text
    })

    if (response.toolCalls.length === 0) {
      nextInput = ""
    } else {
      await runToolCalls(response.toolCalls)
      nextInput = "[tool results available]"
    }

    session.append({ type: "step/end" })
  }

  session.append({ type: "turn/end" })
}

7. 逐层升级

direct callModel      → ctx.llm
direct tools          → ctx.tools
hardcoded prompt      → prompt registry
direct pre logic      → waterfall
Message[]             → SessionEvent projection
single tools          → scheduler
global services       → scoped Context
memory only           → persistence seam
rewrite history       → Surface compaction

8. 两个插件

async function permissionPlugin(call, next) {
  if (isDangerous(call)) throw new Error("Permission denied")
  return next(call)
}

async function metricsPlugin(call, next) {
  const start = Date.now()
  try { return await next(call) }
  finally { console.log(Date.now() - start) }
}

9. Fake FS

interface FileSystem {
  read(path: string): Promise<string>
  write(path: string, value: string): Promise<void>
}

class LocalFS implements FileSystem {}
class MemoryFS implements FileSystem {}

ReadFileTool 只依赖 FileSystem。切换 Provider 时 Consumer 不动,这就是 Seam。