跳到主要内容

架构

约定会腐化,测试不会。

这一页写的每一条都能在代码里定位到文件和行号。 读完之后你应该能自己验证,或者推翻我们。

01 / 分层

依赖方向单向

standard 是宪法层:纯 stdlib,外部依赖只有 github.com/google/uuid。这不是声称,是 go list -deps 能数出来的。

依赖方向
                    cmd / tui / web / desktop
                               │
                               ▼
                    internal/app  (组合根 · 唯一允许装配 provider)
                               │
         ┌─────────────────────┼─────────────────────┐
         ▼                     ▼                     ▼
   agent/runtime          wire/server            wire/rpc
         │                     │                     │
         ├──── model/capability ────  只读能力聚合    │
         │            │                             │
         │     context/ · session/ · store/          │
         │            │                             │
         └──── tools/ · platform/ · a2a/ ────────────┘
                               │
                               ▼
                  foundation/standard  ← 纯 stdlib
                               ▲
                               │
              全部单向依赖,standard 不反向依赖任何业务模块
02 / 门禁

5 条规则,会 fail 的那种。

测试遍历全仓非测试 .go 文件,并且断言必须命中 >0 个文件——否则直接失败,防止目录层级变化导致豁免规则静默失效。

  1. 01

    Provider 隔离

    arch_test.go:87

    除 cmd/、internal/app/、internal/model/ 外,任何文件不得 import openai / anthropic / bedrock / google / azure adapter 或官方 SDK

  2. 02

    Model 层隔离

    arch_test.go:119

    runtime / context / session / tools / memory / tui / config 不得 import pal/internal/model/*

  3. 03

    状态层不反向依赖 runtime

    arch_test.go:132

    extension/mcp、agent/memory、platform/sandbox 不得 import pal/internal/agent/runtime

  4. 04

    语法引擎隔离

    arch_test.go:214

    gotreesitter 只有 platform/syntax/tsengine 能 import

  5. 05

    执行包不读进程目录

    arch_test.go:189

    agent/、tools/、wire/ 不得直接读 App.WorkspaceDir

03 / 执行链路

一个 Turn 是 15 步,★ 标记的是不变量

Turn 可以跑数小时。每个 Cycle 重新冻结一次执行环境,整份 snapshot 序列化后作为 EventCycleStarted 落库——所以「当时用的是哪个模型、哪些工具」是可回溯的事实,不是猜测。

cycleEngine.Run
cycleEngine.Run
────────────────────────────────────────────────────────────
 1  processSteering          消费排队的 steering
 2  读 session projection    progress / task / goal / scope / model
 3  CreateCycleSnapshot      ★ 冻结 provider·model·tools·skills
 4  合并·排序·收窄 scope     应用 ExecutionPolicy 覆盖
 5  compactAtSafePoint       压缩在建请求之前发生
 6  BuildRequest             system sections + 动态段 + tools
 7  EventCycleStarted 落库    ★ 整份 snapshot 序列化后持久化
 8  openProviderStream        重试阶梯
 9  媒体超额 → 降级重试      上限 1 次
10  streams.Consume
11  usage·成本·校准·append   ObserveUsage 回流校准 token 估算
12  ExecuteBatch              或全部拒绝
13  EventCycleCompleted 落库
14  ObserveCycle → Transition
15  pal.finalize              ★ 强制 snapshot.Tools = nil
04 / 取舍

拒绝掉的方案

每一条都是「选了 A 弃了 B」,附代码位置。

回溯只移动指针

选 append EventBranchMoved/弃 删除后续节点

像 git checkout,不是 git reset --hard。回到 20 分钟前不该毁掉那 20 分钟。

记忆不用向量

选 SQL 精确文本匹配/弃 embedding + 向量检索

代码注释直书 "exact text matching, no vectors"。内置打分维度 0 个,排序交给插件——不装 ranker 就返回空,不假装。

标准层零依赖

选 纯 stdlib(外部依赖仅 github.com/google/uuid)/弃 方便但污染的依赖

标准层是宪法。厂商 SDK 升 v4 时,runtime / session / context / memory / tui 一行不用改。

执行快照落库

选 每轮 freeze 后整份 JSON 写进 EventCycleStarted/弃 只在内存里记一份

收尾时强制 Tools=nil——工具集被清空这件事本身也是审计记录。

MCP 工具一律高危

选 外部工具强制标 Risk: High + 网络副作用/弃 按工具声明的风险分级

MCP 是纯客户端。外部 server 返回的内容一律标注 untrusted,不因为它自称可信就降级。

Skill 正文按需读

选 阶段一只注入一行 localskill://<id>/弃 启动时把全部 SKILL.md 灌进系统提示词

正文只在模型自己发起 read 时才加载。技能多了不烧 token。

05 / 测试

L0 – L10 分层,3,383 个函数

CI 故意排除 L6(真实付费 provider)和 L8(soak 时长测试),其余在 Linux / macOS / Windows 三个平台跑 gofmt + vet + build + -race。

层内容CI
L0–L3Unit / Component / Wire / Runtime Integration● 跑
L4Local E2E● 跑
L5Failure / Crash 注入● 跑
L6Real Provider(付费网络)○ 排除
L7Platform E2E● 跑
L8Soak(墙钟)○ 排除
L9Eval / Release● 跑
L10UI E2E(拟人驱动 herdr / browser-use)○ 手动