第 6 讲 · core 六包主线:一次回合的骨架

系列:我在Deepseek harness中增加了桌面版,将逐行代码解析我是怎么增加的,希望你也能创建属于自己的桌面版Agent
本讲目标:走读 packages/core/ 六个核心包的关键导出与依赖关系——session(事件日志)、system-prompt(组装)、tools(管道)、agent(接口)、agent-loop(驱动)、scope(隔离)。目标:掌握"一次回合"的骨架与六包的依赖方向。
方式:关键类型/函数精讲(非全行)——本讲是"支线精讲"。

代码仓库https://github.com/tslcarmack/deepseek-harness-desktop
desktop-app.png
Desktop App 运行效果图


🎯 本讲地图

scope(依赖最底层,纯库)
  ▲
  ├── session(ctx.sessions:事件日志)
  ├── system-prompt(ctx.systemPrompt:提示词组装)
  ├── tools(ctx.tools:工具管道)
  └── agent(ctx.agents:Agent 接口)
        ▲
        └── agent-loop(ctx.agentLoop:唯一实现,最上层)

依赖法则:扩展插件依赖 agent(接口),永不直接依赖 agent-loop(实现)——所以主循环可被替换。这就是"脊梁可换、接口稳定"的架构。


📁 1. scope:作用域原语(packages/core/scope/src/index.ts)

定位:非服务纯库,被 session/system-prompt 无环引用。

export function createScope(ctx: Context, key: ScopeKey, options?: CreateScopeOptions): Scope
export function scopeOf(ctx: Context): ScopeKey | undefined
export function scopeTarget(base: T, key: ScopeKey | undefined): Scoped
export function bindScopeParent(key: ScopeKey, parent: ScopeKey): ScopeParentBinding
export function scopeParentOf(key: ScopeKey): ScopeKey | undefined
export function scopeChainOf(key: ScopeKey | undefined): ScopeKey[]

精讲:

函数 作用
createScope 为某个 key(默认是 Agent 对象)铸一个注册作用域,返回 { ctx, rawDispose, dispose }
scopeOf 从 ctx 取当前作用域 key
scopeTarget 生成带品牌的"路由接收器"——作用域过滤事件用它做 this
bindScopeParent / scopeParentOf / scopeChainOf 作用域继承链(子 agent 可见父级注册)

为什么重要:一个进程跑多个 agent 时,ctx.tools.register() 到底注册给全局还是某个 agent,由作用域决定。ScopedLayers(store.ts)管理"全局层 + 精确作用域层",层空即回收。


📁 2. session:事件日志(packages/core/session/src/index.ts)

定位Session(第 425 行)是追加式事件日志;SessionStore(第 792 行)管理多个 session。

export class Session {
  // 核心方法(示意)
  append(event: SessionEvent): void
  deriveMessages(): Message[]        // 从日志派生模型历史
  fork(source, boundary?, childSessionId?): Session
}
export class SessionStore extends Service {
  // ctx.sessions 的实现
}

精讲:

成员 作用
append 追加一条 SessionEvent(turn/step/user/assistant/tool…),seq 连续
deriveMessages 从日志派生模型看到的 Message[]——从不单独存历史
fork 从边界复制历史生成子会话
adoptSessionEvent / snapshotSessionEvent(第 167/192 行) 事件采纳与快照(lossless JSON 保证)

核心不变量(前几讲反复出现):模型可见即已入日志。第 3 讲 desktop 的每次对话,都在这份日志上追加。


📁 3. system-prompt:提示词组装(packages/core/system-prompt/src/index.ts)

定位ctx.systemPrompt,收集各插件的提示词段 + 工具 schema。

export interface PromptSection {
  name: string
  order: number
  text: string
  // ...
}
export interface ToolProviderResult { ... }
export interface PromptAssembly { ... }
export const PERSONA_SECTION = 'deployment:persona'
export const PERSONA_ORDER = 0

精讲:

  • PromptSection:一段提示词(name + order + text)。插件用 systemPrompt.section(...) 注册自己的段(如第 4 讲 addHarnessSourceSection 注册 harness:source,order = -99)。
  • system-prompt/assemble 瀑布:每步请求前,把所有段按 order 排序组装成最终提示词 + 工具 schema 列表。
  • PERSONA_ORDER = 0:persona(人设)段的默认位置——agent 级 deployment:persona 可遮蔽全局默认。

📁 4. tools:工具管道(packages/core/tools/src/index.ts)

定位ctx.tools,工具注册表 + 守卫执行管道。

export interface ToolDefinition extends ToolSchema {
  output: ToolOutputDefinition
  execute(args: unknown, exec: ToolRunContext): Promise
  finalizeContent?(...): ContentBlock[] | undefined
  timeoutMs?: number
  isConcurrencySafe?(args): boolean
  presentCall? / presentResult?(...)
}
export interface ToolExecutionInput { callId, name, arguments, signal, ... }
export type ToolExecutionMode = { kind: 'parallel' } | { kind: 'exclusive' }

精讲(管道五段 + 守卫):

tools/pre-execute(allow/deny/ask 策略瀑布)
  → ctx.tools.guard()(单调守卫:只能拒绝,无法被后续撤销)
  → tools/execute(around 包装:超时/重试/指标,只可换 signal)
  → tools/post-execute(accept/replace/block 结果变换)
  → finalizeContent(工具定义级末次内容)
  → tools/result(只读观察冻结结果)
  • 第 469-472 行TOOL_ABORTED / TOOL_ABORTED_BEFORE_DISPATCH 两个取消码——取消语义的标准化。
  • 第 494 行 ToolNotFoundError:未知工具映射 UNKNOWN_TOOL,调用失败但不结束回合。

📁 5. agent:Agent 接口(packages/core/agent/src/index.ts)

定位ctx.agents,活 Agent 句柄 + 注册表 + 创建工厂缝。

export interface AgentHandle {
  agent: Agent
  dispose(): Promise
}
export interface AgentFactory {
  create(options: CreateAgentOptions): Promise
  resume(options: ResumeAgentOptions): Promise
}
export class AgentRegistry extends Service { ... }

精讲:

类型 作用
AgentHandle 拥有者句柄:dispose()能力,只有持有者能拆 agent
AgentFactory 创建缝——agent-loop 通过 ctx.agents.setFactory() 注册实现,消费者只依赖 ctx.agents
CreateAgentOptions 新 agent 的完整上下文(session 元数据、seed、per-agent options、setup 回调)

关键设计Agent 接口(packages/core/agent/src/types.ts)定义 send/followup/steer/inject/cancel/whenIdle 等——第 3 讲桌面端"一次对话"最终都汇聚到这些方法。


📁 6. agent-loop:主循环(packages/core/agent-loop/src/index.ts)

定位ctx.agentLoop,Agent 接口的唯一具体实现

export class AgentLoop extends Service implements AgentFactory { ... }
export interface AgentLoopSettings { ... }

精讲(一次回合的主循环骨架):

claim inbox 批(next-step + 一条 next-turn)
  → turn/start(入日志)
  → agent/pre-step 瀑布(改写/拒绝认领的消息)
  → step/start + user/message(入日志)
  → system-prompt/assemble + 派生历史
  → agent/request 瀑布 → llm/stream → assistant/chunk*(入日志)
  → tool/call → tools 管道 → tool/result(入日志)
  → step/end → 若工具还亏欠 → 下一 step
  → agent/turn-stopping(serial 终检)
  → turn/end(入日志)
  • AgentLoop implements AgentFactory:通过 ctx.agents.setFactory() 把自己注册为创建者。
  • 可替换性:UI/hooks/协议插件都依赖 ctx.agents(接口),从不 import agent-loop——想换主循环,注册一个新 factory 即可。

🖼️ 第 6 讲依赖图

fig-06-core-deps.png

图 6-1 · core 六包依赖方向与一次回合数据流


⚙️ 机制小结

  1. 依赖方向单向:scope → {session, system-prompt, tools} → agent → agent-loop。底层是库,上层是驱动。
  2. 接口/实现分离agent 定义契约,agent-loop 提供实现——换主循环不动消费者。
  3. 六包各司其职:日志(session)→ 组装(system-prompt)→ 执行(tools)→ 句柄(agent)→ 驱动(agent-loop)→ 隔离(scope)。
  4. 一次回合 = 六包协奏:认领 → 组装 → 请求 → 工具 → 落日志,每步都有 durable 事件。
  5. 作用域贯穿:tools 注册、事件派发都受 scope 控制——多 agent 隔离的根基。

🧪 动手验证

# 1) 数核心包规模(感受"接口轻、实现重")
cd D:\code\deepseek-harness
wc -l packages/core/agent/src/index.ts packages/core/agent-loop/src/index.ts

# 2) 验证 agent-loop 是唯一实现(搜谁 implements AgentFactory)
grep -rn "implements AgentFactory" packages/ --include="*.ts" | grep -v node_modules

# 3) 在 desktop 树里定位六包行
node apps/cli/lib/bin.js --profile desktop --dump-config | grep -E "session|agent-loop|system-prompt|tools" | head

📚 深入指引

关键文件
scope src/index.ts / src/store.ts
session src/index.ts(Session@425)/ src/types.ts(SessionEventMap)
system-prompt src/index.ts
tools src/index.ts / src/schema.ts / src/presentation.ts
agent src/index.ts / src/types.ts(Agent 接口)
agent-loop src/index.ts(AgentLoop@296)

下一讲预告:把六包串成一条线——从桌面窗口发一句话,到模型回复的完整旅程,每一步对应哪个事件、哪个源码文件。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐