Skip to content

Coding Agent 系统架构设计

Published: at 04:00 PM

本文档从系统设计角度描述一个通用 Coding Agent 系统。它不是 README 式概览,而是说明核心运行模型、模块职责、数据流、扩展机制、安全边界和参考实现中的代码位置。

这个系统可以抽象为:

多种交互前端

前端无关的会话控制层

Agent 内核

模型适配层 + 工具执行层

事件流回传给前端渲染

核心原则是:前端负责交互,Controller 负责会话生命周期,Agent 负责模型与工具循环,Provider 负责模型 API,Tool 负责本地或远程能力执行。

架构目标

该系统的目标不是把某一个界面做成“智能助手”,而是把 coding agent 能力沉到可复用内核中,让不同前端共享同一套执行语义。

主要目标包括:

总体分层

┌─────────────────────────────────────────────────────────────┐
│ 交互前端                                                     │
│ 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 等。

参考实现中的关键位置:

核心请求链路

一次普通用户请求的完整链路如下:

用户输入

前端调用 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 再经过权限、安全和调度逻辑后执行工具。

模块职责边界

交互前端

职责:

边界:

参考实现:

Boot 组装层

职责:

边界:

参考实现:

Controller 会话控制层

职责:

边界:

参考实现:

Agent 内核

职责:

边界:

参考实现:

Provider 模型适配层

职责:

核心接口:

type Provider interface {
    Name() string
    Stream(ctx context.Context, req Request) (<-chan Chunk, error)
}

边界:

参考实现:

Tool 工具执行层

职责:

核心接口:

type Tool interface {
    Name() string
    Description() string
    Schema() json.RawMessage
    Execute(ctx context.Context, args json.RawMessage) (string, error)
    ReadOnly() bool
}

边界:

参考实现:

设计原则

接口优先

模型、工具、事件、审批、hook、ask 都以接口或结构化协议连接。核心层依赖接口而不是具体实现,便于替换模型后端、前端或工具来源。

Registry 解耦

Provider factory 和 Tool 都通过 registry 管理:

这种设计避免 Agent 依赖全局工具集,也允许 MCP server 在会话中热插拔。

前端无关的核心控制层

Controller 是所有前端共同驱动的对象。终端、桌面端、HTTP 服务只是在输入输出形态上不同,不重新实现会话状态、权限审批或 plan mode。

工具与模型可插拔

模型只看到 tool schema 并返回 tool call;Agent 只看到 Tool 接口并执行它。这让内置工具、远程 MCP 工具、技能工具、子 Agent 工具在模型视角下保持一致。

事件驱动 UI

Agent 和 Controller 发出 event.Event,前端订阅并渲染。事件类型包含:

UI 不需要从纯文本日志里反推状态。

权限与执行分离

工具只实现能力,是否允许执行由外层决定:

Provider 抽象

Provider 层把不同模型 API 统一成 streaming chat completion。

一次请求包含:

一次响应被归一化为 Chunk 流:

新增模型后端的路径:

  1. 新建 provider 子目录。
  2. 实现 provider.Provider
  3. init() 中通过 provider.Register(kind, factory) 注册。
  4. 在配置中使用对应 kind、base URL、model、API key env 和额外参数。

Tool 抽象

Tool 层是 Agent 操作宿主机和远程系统的唯一通道。

内置工具通常覆盖这些能力:

工具调度规则:

MCP 与插件扩展

MCP 扩展层把外部 MCP server 适配成 Tool。

连接流程:

读取插件配置

选择 transport:stdio / Streamable HTTP

initialize

tools/list

把每个远程工具包装成 Tool

注册到 tool.Registry

Agent 下一轮即可调用

MCP 工具名使用命名空间:

mcp__<server>__<tool>

这样可以避免不同 server 的工具名冲突,也方便按 server 前缀批量移除。

插件启动策略可以分为:

参考实现:

权限、沙盒与审批

安全边界分为四层。

工具属性

每个工具声明 ReadOnly()

权限策略

权限策略把工具调用映射到:

审批通过 Controller 转成 ApprovalRequest 事件,前端展示后调用 Approve 回传结果。

Plan Mode

plan mode 是只读研究模式:

这样不会改变 prompt prefix 或工具 schema,有利于保持上下文缓存稳定。

沙盒

沙盒主要约束 Shell 和写入边界:

参考实现:

会话与 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()

核心细节:

上下文压缩与长会话

长会话面临的问题是:message history 会持续增长,最终接近模型上下文窗口。

参考实现采用压缩机制:

相关能力:

参考实现:

协调器与多模型协作

Agent 内核可以被更高一层 runner 包装。参考实现中,Controller 持有的是 agent.Runner,而不强制只持有单一 Agent。这允许在不改变前端和 Controller 的情况下接入多模型协作。

典型形态是 planner / executor:

Controller

Runner
  ├─ 单模型:Agent
  └─ 双模型:Coordinator
        ├─ Planner Provider
        └─ Executor Agent

职责边界:

参考实现:

自动规划判定

除了用户手动开启 plan mode,系统还可以根据输入复杂度自动进入规划流程。

自动规划链路:

用户输入

Controller 保留 raw prompt

Auto-plan classifier 判断任务复杂度

必要时切换 plan mode

Agent 先只读研究并产出计划

Controller 请求用户批准后再执行

设计要点:

参考实现:

后台任务与长运行工具

部分工具调用可能持续较久,例如后台 shell、后台子 Agent 或等待中的任务。系统通过 session-scoped job manager 管理这些跨 turn 任务。

Agent 执行工具

工具选择后台运行

jobs.Manager 记录任务

前端通过 Controller 查询运行中任务

下一轮 Compose 注入已完成任务摘要

设计要点:

参考实现:

Ask 交互通道

审批不是唯一会阻塞 Agent 的用户交互。系统还支持模型主动提出结构化问题。

Ask 链路:

模型调用 ask 工具

Agent 从 call context 取得 Asker

Controller 发出 AskRequest 事件

前端展示选项或输入框

用户回答

Controller.AnswerQuestion 回传

ask 工具结果写回模型上下文

Ask 与 Approval 的区别:

参考实现:

Hooks 与可观察自动化

Hook 允许用户或项目在 Agent 生命周期中插入外部自动化。它们不属于工具本体,而是围绕 turn 和 tool call 运行。

常见 hook 点:

安全边界:

参考实现:

Evidence 与步骤验收

计划执行不只依赖模型自述。系统可以记录宿主机可观察到的工具调用凭据,并要求模型在完成步骤时引用这些凭据。

工具调用完成

Agent 记录 evidence receipt

模型调用 complete_step

complete_step 校验同一 turn 中是否存在可观察证据

通过后更新任务状态

设计价值:

参考实现:

Checkpoint、分支与回滚

会话状态不仅包括 message history,也包括工作区文件变更。系统通过 checkpoint、branch 和 rewind 支持恢复与分叉。

Checkpoint

writer 工具执行前,如果能预览 diff,Agent 会触发 pre-edit hook,Controller 将原始文件状态写入 checkpoint。

用途:

Branch

分支用于从当前会话或历史 turn 派生新的对话路径。

用途:

Rewind

Rewind 是恢复动作:

参考实现:

代码智能工具

除了基础文件工具和 MCP 工具,系统还可以接入代码智能能力,例如语言服务和符号图。

LSP 工具

LSP 工具通过语言服务器提供跳转、引用、诊断等能力。它们通常按需启动,避免在启动阶段阻塞。

参考实现:

符号图工具

符号图工具可以把项目索引成图结构,支持跨文件搜索、上下文获取、调用链追踪等能力。它可以作为内置 MCP server 接入,因此在 Agent 看来仍是普通工具。

设计要点:

参考实现:

网络、重试与可观测性

可靠运行还依赖一组横切能力。

网络代理

Provider、余额查询、远程 MCP 和网络工具可能共享代理配置。网络层需要在 boot 阶段解析配置,并向不同 HTTP client 注入一致的代理策略。

参考实现:

Provider 重试

模型请求可能发生瞬时网络错误。Provider 层可通过 retry 通知把重试状态发回 Agent,再由事件流展示给前端。

参考实现:

Usage、成本与缓存诊断

Provider 归一化 token usage 后,Agent 通过事件流输出:

这让前端能展示上下文窗口、缓存命中率和费用估算,而不需要解析模型原始响应。

参考实现:

配置、迁移与启动容错

Boot 层还承担一组启动容错职责:

这些逻辑集中在 Boot 层,避免 CLI、桌面端和 HTTP 入口分别实现一套不一致的初始化流程。

参考实现:

Memory、Skill 与 Slash Command

Memory

Memory 被设计成两类输入:

参考实现:

Skill

Skill 是可发现、可启用、可禁用的工作流说明或子 Agent 能力:

参考实现:

Slash Command

Slash command 用作用户和模型都可触发的高层入口:

参考实现:

多前端复用

系统支持多前端的关键是:所有前端只驱动 Controller,并消费事件流。

CLI

CLI 负责:

参考实现:

HTTP/SSE

HTTP/SSE 入口负责:

参考实现:

ACP

ACP 入口负责把同一个 Agent 控制能力暴露给外部协议客户端。

参考实现:

桌面端

桌面端使用 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 侧暴露:

React 侧通过一个桥接文件调用这些方法,并在非 Wails 浏览器开发环境中使用 mock,以便前端可独立开发。

参考实现:

桌面端桥接边界

桌面端桥接层承担 IPC 适配,不承担 Agent 逻辑:

这种边界保证桌面端只是一个前端,而不是第二套业务内核。

典型场景链路

普通问答

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 结果写回模型

模型调整方案

关键扩展点

新增模型后端

实现位置:

需要完成:

新增内置工具

实现位置:

需要完成:

接入 MCP Server

实现位置:

需要关注:

新增前端入口

需要复用:

新前端只需要:

新增 Slash Command / Skill / Memory 能力

相关位置:

设计要求:

参考实现阅读顺序

建议按以下顺序阅读源码:

  1. cmd/reasonix/main.go
  2. internal/boot/boot.go
  3. internal/control/controller.go
  4. internal/event/event.go
  5. internal/agent/agent.go
  6. internal/agent/session.go
  7. internal/agent/compact.go
  8. internal/provider/provider.go
  9. internal/provider/openai/
  10. internal/tool/tool.go
  11. internal/tool/builtin/
  12. internal/permission/
  13. internal/sandbox/
  14. internal/jobs/
  15. internal/hook/
  16. internal/evidence/
  17. internal/checkpoint/
  18. internal/plugin/
  19. internal/lsp/
  20. internal/codegraph/
  21. internal/memory/
  22. internal/skill/
  23. internal/command/
  24. internal/netclient/
  25. internal/serve/
  26. internal/acp/
  27. desktop/app.go
  28. desktop/frontend/src/lib/bridge.ts
  29. desktop/frontend/src/App.tsx

最小心智模型

阅读或扩展该系统时,可以始终带着这条线:

前端不直接调用模型。
前端只把输入、取消、审批和模式切换交给 Controller。
Controller 负责会话生命周期和用户交互语义。
Agent 负责模型-工具循环和运行时安全检查。
Provider 负责模型协议适配。
Tool 负责本地或远程能力执行。
Registry 负责工具集合解耦。
Event Sink 负责把过程传回前端。

只要这条边界不被打破,新增模型、工具、MCP server、前端入口或长期记忆机制,都可以作为局部扩展接入系统,而不需要重写 agent loop。