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。