本文档从系统设计角度描述一个通用 Coding Agent 系统。它不是 README 式概览,而是说明核心运行模型、模块职责、数据流、扩展机制、安全边界和参考实现中的代码位置。
这个系统可以抽象为:
多种交互前端
↓
前端无关的会话控制层
↓
Agent 内核
↓
模型适配层 + 工具执行层
↓
事件流回传给前端渲染
核心原则是:前端负责交互,Controller 负责会话生命周期,Agent 负责模型与工具循环,Provider 负责模型 API,Tool 负责本地或远程能力执行。
架构目标
该系统的目标不是把某一个界面做成“智能助手”,而是把 coding agent 能力沉到可复用内核中,让不同前端共享同一套执行语义。
主要目标包括:
- 多前端复用:CLI、桌面端、HTTP/SSE 服务等入口共享同一个 Go 内核。
- 模型可替换:Agent 不依赖具体模型厂商,只依赖
provider.Provider接口。 - 工具可扩展:内置工具、MCP 工具、技能工具、子 Agent 工具统一进入
tool.Registry。 - UI 事件驱动:Agent 不直接格式化 UI,而是发出结构化事件,由前端决定如何展示。
- 权限与执行分离:工具是否可执行由权限策略、沙盒和审批机制决定,工具本身只实现能力。
- 长会话可持续:通过上下文压缩、会话归档、checkpoint、memory 等机制维持多轮任务状态。
总体分层
┌─────────────────────────────────────────────────────────────┐
│ 交互前端 │
│ CLI / 桌面 WebView / HTTP-SSE / ACP │
└───────────────────────┬─────────────────────────────────────┘
│ 用户输入、取消、审批、模式切换
▼
┌─────────────────────────────────────────────────────────────┐
│ Controller 会话控制层 │
│ slash command、@ 引用、plan mode、审批、session、memory、MCP │
└───────────────────────┬─────────────────────────────────────┘
│ 普通 turn / 管理命令 / 审批回执
▼
┌─────────────────────────────────────────────────────────────┐
│ Agent 内核 │
│ 模型-工具循环、并发工具调度、上下文压缩、checkpoint、事件发射 │
└───────────────┬─────────────────────────────┬───────────────┘
│ │
▼ ▼
┌─────────────────────────────┐ ┌───────────────────────────┐
│ Provider 模型适配层 │ │ Tool Registry 工具注册表 │
│ Chat API / Streaming Adapter │ │ Built-in / MCP / Skill 等 │
└───────────────┬─────────────┘ └───────────────┬───────────┘
│ │
▼ ▼
┌─────────────────────────────┐ ┌───────────────────────────┐
│ 模型 API │ │ 本地文件、Shell、网络、MCP │
└─────────────────────────────┘ └───────────────────────────┘
所有运行过程通过 event.Sink 输出结构化事件:
reasoning、text、tool_dispatch、tool_result、approval_request、usage、notice、turn_done 等。
参考实现中的关键位置:
cmd/reasonix/main.go:CLI 入口。internal/boot/boot.go:依赖装配与启动组装。internal/control/controller.go:前端无关的会话控制器。internal/agent/agent.go:Agent 主循环与工具调度。internal/provider/provider.go:模型后端抽象与 provider registry。internal/tool/tool.go:工具接口与 per-run registry。internal/plugin/:MCP 客户端与远程工具适配。internal/event/event.go:结构化事件流。desktop/:Wails 桌面端 Go 壳。desktop/frontend/:React/Vite 前端。
核心请求链路
一次普通用户请求的完整链路如下:
用户输入
↓
前端调用 Submit / Send / HTTP endpoint
↓
Controller 解析输入
├─ slash command
├─ @file / @resource 引用
├─ memory 注入
├─ plan mode 状态
└─ background job 摘要
↓
Agent.Run()
↓
Provider.Stream(messages + tool schemas)
↓
模型流式返回 reasoning / text / tool_calls
↓
Agent 收集完整 tool_calls
↓
Tool Registry 查找工具
↓
权限 gate / plan mode / hook / checkpoint preview
↓
执行内置工具或 MCP 工具
↓
工具结果写回 session,作为 tool message
↓
继续下一轮模型调用
↓
模型无 tool_calls,输出最终回答
↓
TurnDone 事件通知前端收尾
该链路的关键点是:模型不会直接执行任何宿主机操作。模型只能生成符合 schema 的 tool call;Agent 再经过权限、安全和调度逻辑后执行工具。
模块职责边界
交互前端
职责:
- 收集用户输入。
- 显示 assistant 文本、reasoning、工具卡片、审批弹窗、状态栏。
- 发送取消、审批、模式切换、模型切换等命令。
- 将结构化事件映射成终端输出、WebView 组件或 SSE 消息。
边界:
- 前端不直接调用模型。
- 前端不自己实现 agent loop。
- 前端不直接执行工具。
- 前端只通过 Controller 暴露的命令方法驱动会话。
参考实现:
- CLI:
internal/cli/ - HTTP/SSE:
internal/serve/ - ACP:
internal/acp/ - 桌面端 Go 桥:
desktop/app.go - 桌面端 React 桥:
desktop/frontend/src/lib/bridge.ts
Boot 组装层
职责:
- 加载配置与环境变量。
- 解析当前模型与可选 planner 模型。
- 创建 Provider 实例。
- 创建 Tool Registry。
- 注册内置工具、MCP 工具、LSP 工具、memory 工具、skill 工具、subagent 工具。
- 装配权限策略、沙盒、hook、memory、skills、commands。
- 创建 Agent、可选 Coordinator、Controller。
边界:
- Boot 负责“把配置变成可运行对象”,不负责 turn 执行。
- 其他层不应重复实现启动装配逻辑。
参考实现:
internal/boot/boot.go
Controller 会话控制层
职责:
- 接收前端命令:
Submit、Cancel、Approve、Compact、NewSession、SetMode等。 - 处理 slash command、custom command、skill command、MCP prompt。
- 解析
@文件引用和 MCP resource 引用。 - 管理 session、resume、branch、checkpoint、history。
- 管理 plan mode 的提案、审批和批准后执行。
- 管理权限审批请求与用户回执。
- 管理 MCP server 的连接、断开、热添加、热移除。
- 将 Agent 事件转发给前端。
边界:
- Controller 不执行模型推理。
- Controller 不实现工具逻辑。
- Controller 不关心 UI 样式,只发出结构化事件。
- Controller 是所有前端共享的会话入口。
参考实现:
internal/control/controller.gointernal/control/slash.gointernal/control/refs.gointernal/control/branches.go
Agent 内核
职责:
- 将用户输入写入 session。
- 每轮把 messages 与 tool schemas 发给 Provider。
- 接收流式文本、reasoning、tool call、usage。
- 将 assistant 消息写入 session。
- 根据 tool call 执行工具并写回 tool message。
- 管理 read-only 工具并发与 writer 工具串行执行。
- 在 plan mode 下阻止写工具。
- 调用权限 gate、PreToolUse / PostToolUse hook。
- 预览 writer 工具 diff 并触发 checkpoint。
- 截断过大的工具输出。
- 检测重复失败循环。
- 执行上下文压缩。
- 发出所有结构化运行事件。
边界:
- Agent 不关心 CLI、桌面端或浏览器如何展示事件。
- Agent 不关心具体模型厂商,只调用 Provider。
- Agent 不关心 MCP 传输细节,只调用 Tool。
参考实现:
internal/agent/agent.gointernal/agent/session.gointernal/agent/compact.gointernal/agent/coordinator.gointernal/agent/task.go
Provider 模型适配层
职责:
- 将统一的
provider.Request转换成具体模型 API 请求。 - 处理流式响应。
- 归一化文本、reasoning、tool call、usage。
- 处理 API 鉴权错误、重试、schema 规范化等 provider 相关逻辑。
核心接口:
type Provider interface {
Name() string
Stream(ctx context.Context, req Request) (<-chan Chunk, error)
}
边界:
- Provider 不执行工具。
- Provider 不管理会话生命周期。
- Provider 不决定权限审批。
- Provider 只负责“模型协议适配”。
参考实现:
internal/provider/provider.gointernal/provider/openai/internal/provider/anthropic/
Tool 工具执行层
职责:
- 用统一接口暴露本地或远程能力。
- 提供模型可见的名称、描述和 JSON Schema。
- 执行模型生成的 JSON 参数。
- 声明是否只读。
- 可选提供 writer 工具的 diff preview。
核心接口:
type Tool interface {
Name() string
Description() string
Schema() json.RawMessage
Execute(ctx context.Context, args json.RawMessage) (string, error)
ReadOnly() bool
}
边界:
- Tool 不关心模型上下文。
- Tool 不直接控制 UI。
- Tool 不自行决定是否需要用户审批。
- Tool 的权限由 Agent 和 permission gate 在执行前处理。
参考实现:
internal/tool/tool.gointernal/tool/builtin/internal/permission/internal/sandbox/
设计原则
接口优先
模型、工具、事件、审批、hook、ask 都以接口或结构化协议连接。核心层依赖接口而不是具体实现,便于替换模型后端、前端或工具来源。
Registry 解耦
Provider factory 和 Tool 都通过 registry 管理:
- provider kind 通过
provider.Register注册。 - 内置工具通过
tool.RegisterBuiltin注册。 - 每次运行创建独立
tool.Registry,把启用的内置工具、MCP 工具和其他运行时工具加入其中。
这种设计避免 Agent 依赖全局工具集,也允许 MCP server 在会话中热插拔。
前端无关的核心控制层
Controller 是所有前端共同驱动的对象。终端、桌面端、HTTP 服务只是在输入输出形态上不同,不重新实现会话状态、权限审批或 plan mode。
工具与模型可插拔
模型只看到 tool schema 并返回 tool call;Agent 只看到 Tool 接口并执行它。这让内置工具、远程 MCP 工具、技能工具、子 Agent 工具在模型视角下保持一致。
事件驱动 UI
Agent 和 Controller 发出 event.Event,前端订阅并渲染。事件类型包含:
TurnStartedReasoningTextMessageToolDispatchToolProgressToolResultApprovalRequestAskRequestUsageNoticeCompactionStartedCompactionDoneTurnDone
UI 不需要从纯文本日志里反推状态。
权限与执行分离
工具只实现能力,是否允许执行由外层决定:
- plan mode 阻止 writer 工具。
- permission policy 决定 allow / ask / deny。
- interactive approver 把 ask 转为前端审批事件。
- sandbox 限制 shell 和文件写入边界。
- hook 可在工具调用前后观察或阻止执行。
Provider 抽象
Provider 层把不同模型 API 统一成 streaming chat completion。
一次请求包含:
Messages:系统、用户、assistant、tool 消息。Tools:当前可调用工具的 JSON Schema。Temperature、MaxTokens等参数。
一次响应被归一化为 Chunk 流:
- 文本增量。
- reasoning 增量。
- tool call 开始事件。
- 完整 tool call。
- token usage。
- done / error。
新增模型后端的路径:
- 新建 provider 子目录。
- 实现
provider.Provider。 - 在
init()中通过provider.Register(kind, factory)注册。 - 在配置中使用对应 kind、base URL、model、API key env 和额外参数。
Tool 抽象
Tool 层是 Agent 操作宿主机和远程系统的唯一通道。
内置工具通常覆盖这些能力:
- 文件读取:
read_file - 文件写入:
write_file - 精确编辑:
edit_file - 多点编辑:
multi_edit - 目录浏览:
ls - 文件匹配:
glob - 文本搜索:
grep - Shell:
bash - 网络读取:
web_fetch - 任务清单:
todo_write - 计划步骤完成:
complete_step
工具调度规则:
- 所有工具 schema 每轮随模型请求发送。
- 只读工具可以按连续批次并发执行。
- writer 工具串行执行,保留模型返回的顺序。
- 未知工具返回错误给模型,不直接崩溃会话。
- 大工具输出会被截断并给前端发出 notice。
- writer 工具如实现
Previewer,执行前可生成 diff 供审批和 checkpoint 使用。
MCP 与插件扩展
MCP 扩展层把外部 MCP server 适配成 Tool。
连接流程:
读取插件配置
↓
选择 transport:stdio / Streamable HTTP
↓
initialize
↓
tools/list
↓
把每个远程工具包装成 Tool
↓
注册到 tool.Registry
↓
Agent 下一轮即可调用
MCP 工具名使用命名空间:
mcp__<server>__<tool>
这样可以避免不同 server 的工具名冲突,也方便按 server 前缀批量移除。
插件启动策略可以分为:
- eager:启动时阻塞握手,工具立即可用。
- lazy:先注册占位工具,首次使用时启动。
- background:先注册占位工具,同时后台启动。
参考实现:
internal/plugin/plugin.gointernal/plugin/transport_stdio.gointernal/plugin/transport_http.gointernal/plugin/lazy.gointernal/plugin/prompts.gointernal/plugin/resources.go
权限、沙盒与审批
安全边界分为四层。
工具属性
每个工具声明 ReadOnly():
- read-only 工具默认更容易自动允许,也可并发执行。
- writer 或不透明工具需要更严格的顺序和权限检查。
- MCP 工具默认不透明,除非 server 声明
readOnlyHint或参考实现明确标注。
权限策略
权限策略把工具调用映射到:
- allow:直接允许。
- ask:发起审批。
- deny:直接阻止。
审批通过 Controller 转成 ApprovalRequest 事件,前端展示后调用 Approve 回传结果。
Plan Mode
plan mode 是只读研究模式:
- Agent 仍向模型暴露同一套工具 schema。
- 执行时阻止非只读工具。
- 模型完成计划后,Controller 发出计划审批。
- 用户批准后退出 plan mode,并允许执行该计划对应的一轮写操作。
这样不会改变 prompt prefix 或工具 schema,有利于保持上下文缓存稳定。
沙盒
沙盒主要约束 Shell 和写入边界:
- 文件写工具绑定 workspace / write roots。
- Shell 可按配置进入受限模式。
- 网络访问可按配置限制。
- 不支持沙盒的平台会退化并给出 warning。
参考实现:
internal/permission/internal/sandbox/internal/tool/builtin/confine.gointernal/tool/builtin/bash.go
会话与 Agent Loop
Agent session 保存系统消息、用户消息、assistant 消息和 tool 消息。
主循环伪代码:
session.add(user_input)
loop:
schemas = registry.schemas()
chunks = provider.stream(session.messages, schemas)
assistant_message = collect(chunks)
session.add(assistant_message)
if assistant_message.tool_calls is empty:
return final_answer
results = execute_tool_calls(assistant_message.tool_calls)
for each result:
session.add(tool_message(result))
maybe_compact()
核心细节:
- provider 请求前会读取当前 registry schema,因此热添加的工具在下一轮生效。
- 工具结果按 call 顺序写回,满足模型 API 的 tool-call pairing 约束。
- 如果 provider 返回异常 finish reason,会通过 notice 告诉前端。
- 如果同一批工具反复以相同错误失败,loop guard 会提示模型换方法。
上下文压缩与长会话
长会话面临的问题是:message history 会持续增长,最终接近模型上下文窗口。
参考实现采用压缩机制:
- 根据 context window 与阈值判断是否需要压缩。
- 保留最近消息尾部。
- 将较早的中间消息总结成 compact summary。
- 原始消息可归档到 archive。
- 压缩完成后通过
CompactionDone事件通知前端。
相关能力:
/compact手动触发压缩。- 自动压缩在接近阈值时触发。
- 压缩失败或无法降到阈值以下时会暂停自动压缩,避免循环。
参考实现:
internal/agent/compact.gointernal/control/controller.go
协调器与多模型协作
Agent 内核可以被更高一层 runner 包装。参考实现中,Controller 持有的是 agent.Runner,而不强制只持有单一 Agent。这允许在不改变前端和 Controller 的情况下接入多模型协作。
典型形态是 planner / executor:
Controller
↓
Runner
├─ 单模型:Agent
└─ 双模型:Coordinator
├─ Planner Provider
└─ Executor Agent
职责边界:
- Planner 负责判断任务、拆解方向或生成执行建议。
- Executor 负责实际 tool loop 和宿主机操作。
- Coordinator 负责在两者之间编排,但仍通过同一个事件流向前端报告阶段变化。
- Controller 不需要知道底层 runner 是单 Agent 还是 Coordinator。
参考实现:
internal/agent/coordinator.gointernal/boot/boot.go
自动规划判定
除了用户手动开启 plan mode,系统还可以根据输入复杂度自动进入规划流程。
自动规划链路:
用户输入
↓
Controller 保留 raw prompt
↓
Auto-plan classifier 判断任务复杂度
↓
必要时切换 plan mode
↓
Agent 先只读研究并产出计划
↓
Controller 请求用户批准后再执行
设计要点:
- 分类器是 Controller 层能力,不属于前端。
- raw prompt 与展开后的
@文件内容分开,避免引用内容放大复杂度评分。 - 自动规划只改变会话模式,不改变 Agent 的工具 schema。
参考实现:
internal/control/auto_plan.gointernal/control/auto_plan_classifier.gointernal/boot/boot.go
后台任务与长运行工具
部分工具调用可能持续较久,例如后台 shell、后台子 Agent 或等待中的任务。系统通过 session-scoped job manager 管理这些跨 turn 任务。
Agent 执行工具
↓
工具选择后台运行
↓
jobs.Manager 记录任务
↓
前端通过 Controller 查询运行中任务
↓
下一轮 Compose 注入已完成任务摘要
设计要点:
- 后台任务生命周期绑定 session,而不是单个 turn。
- Controller 关闭时会取消仍在运行的任务。
- 前端只展示任务状态,不直接持有进程句柄。
- 后台任务完成后的输出会以摘要形式进入后续上下文。
参考实现:
internal/jobs/internal/tool/builtin/bash.gointernal/tool/builtin/bgjobs.gointernal/agent/task.go
Ask 交互通道
审批不是唯一会阻塞 Agent 的用户交互。系统还支持模型主动提出结构化问题。
Ask 链路:
模型调用 ask 工具
↓
Agent 从 call context 取得 Asker
↓
Controller 发出 AskRequest 事件
↓
前端展示选项或输入框
↓
用户回答
↓
Controller.AnswerQuestion 回传
↓
ask 工具结果写回模型上下文
Ask 与 Approval 的区别:
- Approval 是宿主机安全 gate,回答是否允许执行。
- Ask 是任务信息收集,回答模型需要的业务或实现偏好。
- Headless 场景下 Ask 可以退化为让模型自行决策。
参考实现:
internal/agent/ask.gointernal/event/event.gointernal/control/controller.go
Hooks 与可观察自动化
Hook 允许用户或项目在 Agent 生命周期中插入外部自动化。它们不属于工具本体,而是围绕 turn 和 tool call 运行。
常见 hook 点:
UserPromptSubmit:用户输入提交后、模型调用前。PreToolUse:工具执行前,可阻止工具调用。PostToolUse:工具执行后,用于观察或通知。PostLLMCall:模型调用后,可处理 reasoning 展示。Stop:一次用户 turn 结束时。PreCompact:上下文压缩前,为总结提供额外指导。SessionEnd:会话关闭时。
安全边界:
- 项目 hook 可能执行任意 shell 命令,因此需要 trust 机制。
- hook 输出通过事件流展示给用户,而不是直接污染 UI。
PreToolUse的阻止结果会反馈给模型,让模型换方法。
参考实现:
internal/hook/internal/control/controller.gointernal/agent/agent.go
Evidence 与步骤验收
计划执行不只依赖模型自述。系统可以记录宿主机可观察到的工具调用凭据,并要求模型在完成步骤时引用这些凭据。
工具调用完成
↓
Agent 记录 evidence receipt
↓
模型调用 complete_step
↓
complete_step 校验同一 turn 中是否存在可观察证据
↓
通过后更新任务状态
设计价值:
- 避免模型在没有运行验证、没有读取文件、没有完成编辑时声称任务完成。
- 将计划步骤与实际工具行为绑定。
- 支持项目级验证指令,例如要求写入后运行特定检查。
参考实现:
internal/evidence/internal/instruction/internal/tool/builtin/completestep.gointernal/tool/builtin/todo.go
Checkpoint、分支与回滚
会话状态不仅包括 message history,也包括工作区文件变更。系统通过 checkpoint、branch 和 rewind 支持恢复与分叉。
Checkpoint
writer 工具执行前,如果能预览 diff,Agent 会触发 pre-edit hook,Controller 将原始文件状态写入 checkpoint。
用途:
- 恢复某一轮前的代码状态。
- 支持仅回滚代码、仅回滚对话、或两者一起回滚。
- 为前端展示可恢复点提供元数据。
Branch
分支用于从当前会话或历史 turn 派生新的对话路径。
用途:
- 保留原始探索路径。
- 从某个 turn 分叉尝试不同方案。
- 在 UI 中展示会话树。
Rewind
Rewind 是恢复动作:
- conversation rewind:截断或恢复消息历史。
- code rewind:恢复 checkpoint 中的文件快照。
- both:同时恢复对话与代码。
参考实现:
internal/checkpoint/internal/control/branches.gointernal/control/controller.godesktop/frontend/src/components/HistoryPanel.tsx
代码智能工具
除了基础文件工具和 MCP 工具,系统还可以接入代码智能能力,例如语言服务和符号图。
LSP 工具
LSP 工具通过语言服务器提供跳转、引用、诊断等能力。它们通常按需启动,避免在启动阶段阻塞。
参考实现:
internal/lsp/
符号图工具
符号图工具可以把项目索引成图结构,支持跨文件搜索、上下文获取、调用链追踪等能力。它可以作为内置 MCP server 接入,因此在 Agent 看来仍是普通工具。
设计要点:
- 初始索引可后台准备,避免阻塞启动。
- 工具通过 MCP 命名空间注册。
- 只读工具可参与并发调度和较宽松的权限策略。
参考实现:
internal/codegraph/internal/plugin/
网络、重试与可观测性
可靠运行还依赖一组横切能力。
网络代理
Provider、余额查询、远程 MCP 和网络工具可能共享代理配置。网络层需要在 boot 阶段解析配置,并向不同 HTTP client 注入一致的代理策略。
参考实现:
internal/netclient/internal/sysproxy/
Provider 重试
模型请求可能发生瞬时网络错误。Provider 层可通过 retry 通知把重试状态发回 Agent,再由事件流展示给前端。
参考实现:
internal/provider/retry.gointernal/event/event.go
Usage、成本与缓存诊断
Provider 归一化 token usage 后,Agent 通过事件流输出:
- 单次 turn token 使用量。
- session 累计 cache hit / miss。
- 可选成本估算。
- prompt prefix 是否变化及变化原因。
这让前端能展示上下文窗口、缓存命中率和费用估算,而不需要解析模型原始响应。
参考实现:
internal/agent/cache_shape.gointernal/provider/provider.gointernal/billing/
配置、迁移与启动容错
Boot 层还承担一组启动容错职责:
- 迁移旧配置和旧 session。
- 解析 workspace root 对应的配置、skills、commands、hooks、memory。
- 校验模型配置与 API key。
- 在缺少 key 但需要 UI 可访问时,先启动并发出 notice。
- 根据配置启用或禁用工具、沙盒、MCP、LSP、memory、skills。
- 对启动失败的非关键扩展发出 notice,而不是直接终止整个会话。
这些逻辑集中在 Boot 层,避免 CLI、桌面端和 HTTP 入口分别实现一套不一致的初始化流程。
参考实现:
internal/boot/boot.gointernal/config/internal/event/event.go
Memory、Skill 与 Slash Command
Memory
Memory 被设计成两类输入:
- 稳定记忆:在启动时折入系统 prompt prefix。
- 会话内新记忆:作为下一轮 transient note 注入,避免立即破坏缓存稳定前缀。
参考实现:
internal/memory/
Skill
Skill 是可发现、可启用、可禁用的工作流说明或子 Agent 能力:
- skill index 可折入系统 prompt。
- skill body 按需加载。
- inline skill 可渲染为当前会话 prompt。
- subagent skill 可在隔离 Agent 中运行。
参考实现:
internal/skill/internal/agent/task.go
Slash Command
Slash command 用作用户和模型都可触发的高层入口:
- 内置管理命令:如 compact、new、model、memory、mcp、skill。
- 项目或用户自定义 command。
- MCP prompt。
- skill command。
参考实现:
internal/command/internal/control/slash.gointernal/cli/skillslash.go
多前端复用
系统支持多前端的关键是:所有前端只驱动 Controller,并消费事件流。
CLI
CLI 负责:
- 解析命令行参数。
- 调用 boot 组装 Controller。
- 将终端输入交给 Controller。
- 用 TUI 或纯文本 sink 渲染事件。
参考实现:
cmd/reasonix/main.gointernal/cli/
HTTP/SSE
HTTP/SSE 入口负责:
- 接收浏览器请求。
- 将输入提交给 Controller。
- 将 event stream 广播给客户端。
- 提供 history、model switch 等 API。
参考实现:
internal/serve/
ACP
ACP 入口负责把同一个 Agent 控制能力暴露给外部协议客户端。
参考实现:
internal/acp/
桌面端
桌面端使用 Wails + Go + React/Vite:
React 组件
↓
bridge.ts
↓
Wails binding: window.go.main.App.*
↓
desktop/app.go
↓
control.Controller
↓
agent.Agent
↓
provider / tool
↓
event.Sink
↓
runtime.EventsEmit("agent:event")
↓
React 渲染
Go 侧暴露:
SubmitCancelApproveAnswerQuestionSetModeCompactNewSessionHistoryModelsSetModelCapabilitiesSettingsMemory
React 侧通过一个桥接文件调用这些方法,并在非 Wails 浏览器开发环境中使用 mock,以便前端可独立开发。
参考实现:
desktop/app.godesktop/frontend/src/lib/bridge.tsdesktop/frontend/src/App.tsx
桌面端桥接边界
桌面端桥接层承担 IPC 适配,不承担 Agent 逻辑:
- Go App 方法是 Controller 的薄包装。
- React 不直接访问 Go 内核对象。
- 事件通道是桌面端 UI 状态更新的主要来源。
- mock binding 只用于前端开发,不改变真实运行链路。
这种边界保证桌面端只是一个前端,而不是第二套业务内核。
典型场景链路
普通问答
Submit("解释这个函数")
↓
Controller 组合输入
↓
Agent 调用 Provider
↓
模型返回文本,无 tool_calls
↓
Message + TurnDone 事件
文件读取
用户询问某个文件
↓
模型调用 read_file
↓
Agent 确认 read_file 为只读
↓
执行工具
↓
结果作为 tool message 写回
↓
模型基于文件内容回答
文件编辑
模型调用 edit_file / write_file / multi_edit
↓
Agent 生成 diff preview
↓
权限 gate 判断是否需要审批
↓
前端显示 ApprovalRequest
↓
用户批准
↓
执行写工具
↓
checkpoint 保存预编辑快照
↓
ToolResult + Message + TurnDone
Shell 命令执行
模型调用 bash
↓
permission policy 判断 allow / ask / deny
↓
sandbox 根据配置限制 shell 能力
↓
执行命令并流式输出 ToolProgress
↓
结果截断后写回模型上下文
Plan Mode 审批后执行
用户开启 plan mode
↓
Agent 允许只读工具,阻止 writer 工具
↓
模型产出计划
↓
Controller 发起 exit_plan_mode 审批
↓
用户批准计划
↓
Controller 关闭 plan mode 并种子化 todo
↓
Agent 执行批准后的实现 turn
MCP 工具调用
MCP server 已连接或 lazy 启动
↓
远程工具注册为 mcp__server__tool
↓
模型调用该工具
↓
Agent 从 Registry 取到 remoteTool
↓
remoteTool 发起 JSON-RPC tools/call
↓
MCP 返回 content
↓
结果写回模型上下文
Skill 调用
用户输入 /review 或模型调用 run_skill
↓
Controller / Tool 渲染 skill body
↓
inline skill 进入当前 turn
或
subagent skill 创建隔离 Agent
↓
结果回到主会话
Ask 工具提问
模型缺少决策信息
↓
模型调用 ask
↓
Controller 发出 AskRequest
↓
前端展示结构化问题
↓
用户回答
↓
答案作为工具结果写回模型
后台任务完成
模型启动后台 bash / task
↓
jobs.Manager 记录任务
↓
前端状态栏显示运行中任务
↓
任务完成
↓
下一轮输入组合时注入完成摘要
回滚一次编辑
writer 工具执行前生成 preview
↓
checkpoint 保存原始文件状态
↓
用户选择 rewind
↓
Controller 恢复 code / conversation / both
↓
前端重新读取 history 和工作区状态
Hook 阻止工具
模型调用工具
↓
权限 gate 允许
↓
PreToolUse hook 运行
↓
hook 返回阻止
↓
Agent 把 blocked 结果写回模型
↓
模型调整方案
关键扩展点
新增模型后端
实现位置:
internal/provider/<kind>/
需要完成:
- 实现
provider.Provider。 - 把请求 messages 和 tools 转为目标 API 格式。
- 把流式响应转为
provider.Chunk。 - 在
init()中注册 factory。 - 在配置中增加 provider kind。
新增内置工具
实现位置:
internal/tool/builtin/
需要完成:
- 实现
tool.Tool。 - 明确
ReadOnly()。 - 为参数提供 JSON Schema。
- 如是 writer 工具,尽量实现
tool.Previewer。 - 注册到内置工具集合。
- 添加针对权限、路径边界、错误处理的测试。
接入 MCP Server
实现位置:
- 用户配置或运行时添加。
- 核心适配在
internal/plugin/。
需要关注:
- transport 类型。
- server 名称稳定性。
- 工具 read-only hint。
- lazy / eager / background 启动策略。
- 失败诊断与重连。
新增前端入口
需要复用:
boot.Buildcontrol.Controllerevent.Sink
新前端只需要:
- 把用户输入转成 Controller 命令。
- 实现事件 sink 或事件广播。
- 实现审批和 ask 回执。
- 展示 session、工具、usage、notice 等状态。
新增 Slash Command / Skill / Memory 能力
相关位置:
- slash command:
internal/command/、internal/control/slash.go - skill:
internal/skill/ - memory:
internal/memory/
设计要求:
- 不把长期、动态的大文本直接塞进每轮 prompt。
- 优先用索引进入稳定 prefix,正文按需加载。
- 会话内变更通过 transient 注入过渡到下一会话稳定 prefix。
参考实现阅读顺序
建议按以下顺序阅读源码:
cmd/reasonix/main.gointernal/boot/boot.gointernal/control/controller.gointernal/event/event.gointernal/agent/agent.gointernal/agent/session.gointernal/agent/compact.gointernal/provider/provider.gointernal/provider/openai/internal/tool/tool.gointernal/tool/builtin/internal/permission/internal/sandbox/internal/jobs/internal/hook/internal/evidence/internal/checkpoint/internal/plugin/internal/lsp/internal/codegraph/internal/memory/internal/skill/internal/command/internal/netclient/internal/serve/internal/acp/desktop/app.godesktop/frontend/src/lib/bridge.tsdesktop/frontend/src/App.tsx
最小心智模型
阅读或扩展该系统时,可以始终带着这条线:
前端不直接调用模型。
前端只把输入、取消、审批和模式切换交给 Controller。
Controller 负责会话生命周期和用户交互语义。
Agent 负责模型-工具循环和运行时安全检查。
Provider 负责模型协议适配。
Tool 负责本地或远程能力执行。
Registry 负责工具集合解耦。
Event Sink 负责把过程传回前端。
只要这条边界不被打破,新增模型、工具、MCP server、前端入口或长期记忆机制,都可以作为局部扩展接入系统,而不需要重写 agent loop。