目标:如果你不复制官方源码,而是从空目录开始重建一个架构等价的 DSH,这一页就是实施蓝图。
1. 先定仓库边界,不要先写 UI
建议把“稳定契约”和“可替换实现”拆成独立 package。下面不是官方目录的逐字复制,而是根据 DSH 的能力 seam 抽出的学习型等价结构。
packages/
core/
context/ # plugin/effect/scope runtime facade
session/ # append-only events + projection
agent/ # Agent interface + registry + factory contract
agent-loop/ # only concrete turn/step driver
tools/ # definition registry + guarded pipeline
system-prompt/ # sections/variables/tool-schema assembly
llm/ # request/message/stream vocabulary + adapter registry
providers/
llm-deepseek/
fs-local/
shell-local/
session-jsonl/
session-sqlite/
consumers/
tool-read/
tool-bash/
tool-terminal/
boot/
app-boot/
profile-loader/
apps/
headless/
web/2. 七个先冻结的接口
| 接口 | 必须稳定的内容 | 暂时不要冻结 |
|---|---|---|
| Session | append、events、deriveMessages、fork/resume 边界 | 具体磁盘格式 |
| Agent | id、ctx、send/cancel/whenIdle、registry lifecycle | 具体 loop class |
| LLM | ModelRequest、StreamChunk、LlmError | provider wire JSON |
| Tools | ToolDefinition、visibility、execute pipeline | bash/read 的实现 |
| Prompt | section/variable/tool schema assembly | 具体 persona 文案 |
| Scope | global → agent 的解析规则 | UI 状态 |
| Boot | 配置层合成 → 插件树 → rollback | 某个 profile 名称 |
3. Session 数据结构:先设计事件,再设计类
type SessionEvent = {
seq: number
time: number
type: string
data: JsonValue
ignorable?: true
}
interface Session {
readonly id: string
append(type: string, data: JsonValue, meta?: SurfaceMeta): SessionEvent
deriveMessages(): ModelMessage[]
snapshot(): readonly SessionEvent[]
}关键规则:seq 单调;事件一旦 append 就不可改;未知且非 ignorable 的事件在恢复时拒绝;所有模型可见输入必须存在可回放来源。
4. Agent 发布事务
prepare session
prepare scoped context
prepare concrete agent
run setup(scope)
validate commit hooks
enter session registry
enter agent registry
announce session/created
announce agent/created
start driver
on any failure:
stop private driver if started
dispose scope
detach exact agent entry
detach exact session entry不要把“创建对象”与“让其他组件看见对象”混在一起。publication boundary 是竞争条件和回滚正确性的中心。
5. Turn/Step driver 的最小状态机
wake -> running
open turn
claim inbox batch
pre-step waterfall
if rejected: close turn
else:
repeat:
open step
derive history from log
assemble prompt + visible tools
request model
persist raw chunks + final assistant anchor
execute proposed tools through guarded pipeline
close step
claim follow-up / steering
until no work remains
turn-stopping serial checkpoint
close turn
flush durability
idle6. 工具执行必须有“权威执行点”
工具调用从模型出来后不能直接 definition.execute()。所有调用必须穿过同一个 pipeline,才能统一实现审批、沙箱、超时、审计、结果规范化和取消。任何绕过这个执行点的“方便 API”都会成为安全漏洞。
7. LLM streaming 设计
Adapter 输出应是闭合 discriminated union;Loop 记录 raw chunk 以保证 UI fidelity,同时由共享 assembler 折叠成最终 assistant message。不要让每个 provider 自己决定 Session 怎么记。
8. Scope 的最低验收
同时创建 A/B 两个 agent:全局注册 read;仅 A scoped 注册 bash;仅 B scoped persona 改名。A 的 prompt/tools 不应污染 B。销毁 A 后,B 仍正常运行,且全局 registry 不残留 A 的 effect。
9. Boot/Config 的最低验收
- 基础 bundle 提供默认 LLM、tools、persistence。
- profile patch 能替换 LLM provider。
- CLI overlay 能临时关闭一个工具。
- 任何挂载失败都会 dispose 已成功挂载的前置插件。
- dump-config 的结果与实际 mount tree 一致。
10. 生产级补齐顺序
| 阶段 | 新增 | 先写的测试 |
|---|---|---|
| P0 | mock LLM + 1 tool + event log | 两 step 工具循环 |
| P1 | scoped context | 两个 agent 能力隔离 |
| P2 | DeepSeek stream adapter | chunk 组装与 abort |
| P3 | JSONL persistence/resume | 重启后模型历史等价 |
| P4 | tool policy/scheduler | 独占与并行顺序 |
| P5 | profile/bundle/patch | provider 仅靠配置替换 |
| P6 | Web projection | 刷新 UI 不改变事实状态 |
| P7 | HMR / lifecycle hardening | 反复 reload 无资源泄漏 |
11. 一模一样的真正判据
不要用“页面像不像”验收。用契约验收:事件语义、scope 隔离、provider 可替换、失败回滚、持久恢复、取消传播、工具调度、配置叠层。如果这些行为一致,才是 harness 层面的等价;UI 只是最后一层。