Skip to content
页面导航
精简

AgentScope-PAW 启动原理详解

本文档面向初学者,从零开始拆解 agentscope-paw 项目从构建到运行的每一个环节。 PAW(内部代号 Claw2)是一个本地单用户 AI Agent 助手平台。


一、项目概览

1.1 PAW 是什么?

AgentScope-PAW 是一个 本地单用户 AI Agent 助手平台,提供可视化 Web UI 让用户与 Agent 对话、创建自定义 Agent、管理技能和会话。它是一个前后端一体项目:

技术说明
后端Spring Boot WebFlux (响应式)Netty 服务器、无认证(单用户本地使用)、SSE 流式对话
前端React + Vite SPA打包为静态资源嵌入后端 JAR
Agent 框架HarnessAgent + ClawBootstrap本地单 Agent + 自定义 Agent 懒加载
模型DashScope qwen-max (默认)支持 OpenAI / Anthropic 等自定义 Model Bean
持久化JSON 文件 (无数据库)agentscope.json、agents.json、sessions.json

关键区别:与 Builder 不同,PAW 不使用数据库(没有 JPA/H2),所有持久化状态都存储在 JSON 文件中。也没有认证系统——设计为单用户本地部署。

1.2 核心技术栈

┌───────────────────────────────────────────────────────┐
│  浏览器 → http://localhost:8080                        │
│  ┌───────────────┐  ┌──────────────────────────────┐  │
│  │  React SPA    │  │  Spring Boot WebFlux         │  │
│  │  (前端页面)    │←│  REST API + SSE 流式聊天     │  │
│  └───────────────┘  └──────────────────────────────┘  │
│                      │                                │
│                      ├─ Security (全开放 permitAll)   │
│                      ├─ SPA Fallback (index.html)     │
│                      └─ BuilderConfig                 │
│                         │                             │
│                         ├─ ClawBootstrap (核心)        │
│                         │  ├─ HarnessAgent (内置)      │
│                         │  ├─ SessionAgentManager      │
│                         │  ├─ HarnessGateway           │
│                         │  ├─ SessionsTool             │
│                         │  ├─ OutboundTool             │
│                         │  └─ ChatUiChannel            │
│                         ├─ DashScopeChatModel          │
│                         │     │                        │
│                         │     ↓                        │
│                         │  DashScope API (通义千问)     │
│                         ├─ ToolEventBus + Middleware   │
│                         └─ ChannelManager (IM 通道)    │
│                            ├─ DingTalk / Feishu / WeCom│
│                            └─ GitHub / GitLab          │
└───────────────────────────────────────────────────────┘

1.3 与 Builder 的关键差异

特性BuilderPAW (Claw2)
定位Agent 构建平台(多人协作)本地助手(单用户)
认证JWT + 多用户无认证(permitAll)
数据库JPA + H2/MySQL纯 JSON 文件
BootstrapBuilderBootstrap(3 Phase)ClawBootstrap(Builder 模式)
会话隔离DmScope.PER_PEER(每用户独立)DmScope.MAIN(全局共享)
自定义 Agent无(预定义)懒加载注册(首次对话才构建)
主类包名io.agentscope.builderio.agentscope.claw2

二、构建过程详解

2.1 Maven 多模块依赖链

PAW 是 agentscope-java 多模块项目的一个子模块。它的 Maven 构建依赖链:

agentscope-parent (根 POM)
  ├─ agentscope-dependencies-bom     ← BOM(版本管理)
  ├─ agentscope-distribution/agentscope-bom ← 运行时 BOM
  ├─ agentscope-core                  ← 核心框架
  ├─ agentscope-harness               ← Agent 运行时
  ├─ agentscope-extensions-model-dashscope  ← DashScope 模型
  ├─ agentscope-extensions-channel-*  ← IM 通道扩展
  └─ agentscope-examples/agents/agentscope-paw ← 本项目

2.2 为什么需要从根目录构建?

PAW 的 pom.xml 使用 ${revision} 引用父 POM 和 BOM,这些 SNAPSHOT 依赖在 Maven 本地仓库中不存在。如果直接在 PAW 子目录运行 mvn exec:java,Maven 会报错找不到 BOM。

解决方案:从项目根目录运行 Maven,利用 Reactor(Maven 多模块构建机制)自动在内存中解析所有模块间依赖,无需先 mvn install 到本地仓库。

bash
# ❌ 错误方式:在子目录单独运行
cd agentscope-paw && mvn exec:java

# ✅ 正确方式:从根目录指定模块
cd agentscope-java && mvn -pl agentscope-examples/agents/agentscope-paw -am exec:java

# ✅ 更好的方式:先打包成 fat JAR,再 java -jar
cd agentscope-java && mvn clean package -DskipTests -pl agentscope-examples/agents/agentscope-paw -am
java -jar agentscope-paw/target/agentscope-paw-2.0.0-SNAPSHOT.jar

-pl 指定构建模块,-am(also-make)自动构建该模块的依赖模块。

2.3 Fat JAR 的形成

PAW 使用 spring-boot-maven-pluginrepackage goal 将编译产物打包成一个可独立运行的 Fat JAR

agentscope-paw-2.0.0-SNAPSHOT.jar  ← Fat JAR(约 50MB)
  ├─ BOOT-INF/classes/              ← Java 类文件 + 配置文件
  │   ├─ io/agentscope/claw2/       ← 后端代码
  │   ├─ static/                    ← 前端打包产物(React SPA)
  │   │   ├─ index.html
  │   │   ├─ assets/*.js, *.css
  │   │   └─ favicon.ico
  │   ├─ scaffold/default/          ← Workspace 脚手架模板
  │   └─ application.yml            ← Spring Boot 配置
  ├─ BOOT-INF/lib/                  ← 所有依赖 JAR(Spring、Netty、agentscope-core 等)
  └─ META-INF/                      ← MANIFEST.MF (Main-Class: spring-boot launcher)

运行 java -jar agentscope-paw.jar 时,Spring Boot Launcher 自动解压依赖到临时目录,加载所有 JAR,然后调用 Claw2App.main()

2.4 前端自动构建

frontend-maven-plugin 在 Maven 的 generate-resources 阶段自动构建前端:

  1. 安装 Node.js — 下载 v20.19.2target/ 目录
  2. 安装 npm 依赖 — 在 frontend/ 目录执行 npm install
  3. 构建前端 — 执行 npm run build,Vite 将 React 项目编译为静态文件
  4. 输出到 classpath:/static/ — Vite 配置 outDir: ../src/main/resources/static,直接放入资源目录

最终 spring-boot-maven-pluginstatic/ 目录打包进 Fat JAR,后端就能通过 Netty 服务器直接提供前端页面。


三、启动过程详解(7 个阶段)

阶段 1:JVM 启动 + Spring Boot 自动配置

java -jar agentscope-paw.jar
  → Spring Boot Launcher 解压 BOOT-INF/lib/ 中的 JAR
  → 创建 Spring ApplicationContext
  → 扫描 @SpringBootApplication → io.agentscope.claw2 包
  → 自动发现并注册所有 @Configuration、@Component、@RestController

Claw2App.main() 是整个应用的入口点:

java
@SpringBootApplication
public class Claw2App {
    public static void main(String[] args) {
        SpringApplication.run(Claw2App.class, args);
    }
}

@SpringBootApplication 是一个组合注解,相当于:

  • @Configuration — 声明这是一个配置类
  • @EnableAutoConfiguration — 让 Spring Boot 根据依赖自动配置
  • @ComponentScan — 自动扫描当前包及子包下的所有组件

Spring Boot 自动配置的核心工作(WebFlux 版):

  1. 发现 spring-boot-starter-webflux → 配置 Netty 作为 HTTP 服务器(不是 Tomcat)
  2. 发现 spring-boot-starter-security → 配置 SecurityWebFilterChain
  3. 发现 spring-boot-starter-actuator → 注册 /actuator/health/actuator/info
  4. 加载 application.yml → 设置端口 8080(可通过 CLAW_PORT 环境变量覆盖)

阶段 2:Security 配置(全开放)

java
// SecurityConfig.java — 无认证,全开放
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http.csrf(ServerHttpSecurity.CsrfSpec::disable)     // 禁用 CSRF
                .cors(cors -> cors.configurationSource(...))        // 允许 localhost CORS
                .authorizeExchange(auth -> auth.anyExchange().permitAll())  // 全开放
                .build();
    }
}

为什么不需要认证? PAW 设计为单用户本地部署应用,运行在个人电脑上,所有端点开放访问。CORS 仅允许 localhost:* 来源,用于支持 Vite 开发模式(http://localhost:5173)。

阶段 3:BuilderConfig — 核心 Bean 组装

BuilderConfig 是 PAW 的核心配置类,负责组装所有关键 Bean:

java
@Configuration
public class BuilderConfig {

    // Bean 1: Model(条件创建)
    @Bean
    @ConditionalOnMissingBean(Model.class)
    @ConditionalOnExpression("'${claw.dashscope.api-key:}' != ''")
    public Model dashscopeModel() {
        return DashScopeChatModel.builder()
                .apiKey(dashscopeApiKey)
                .modelName(dashscopeModelName)  // 默认 qwen-max
                .stream(dashscopeStream)         // 默认 true
                .build();
    }

    // Bean 2: ClawBootstrap(核心)
    @Bean
    public ClawBootstrap builderBootstrap(Optional<Model> modelOpt, ToolEventBus toolEventBus) {
        // 1. 解析 clawHome 路径
        // 2. 自动生成 agentscope.json(如果不存在)
        // 3. 构建 ClawBootstrap → 组装所有 Agent 和 Channel
        // 4. 启动 Channel → 开始监听消息
    }

    // Bean 3: ChatUiChannel
    @Bean
    public ChatUiChannel chatUiChannel(ClawBootstrap bootstrap) {
        return bootstrap.channelManager().getChannel(ChatUiChannel.CHANNEL_ID)...;
    }
}

Model 创建优先级

  1. 如果已有其他 @Configuration 注册了 Model Bean → 直接使用
  2. 如果 claw.dashscope.api-key 不为空 → 创建 DashScopeChatModel
  3. 如果两者都没有 → 启动失败,抛出 IllegalStateException

阶段 4:ClawBootstrap 构建(Builder 模式,3 Phase)

ClawBootstrap 是整个 PAW Agent 运行时的核心。它使用 Builder 模式,内部分 3 个 Phase 构建:

Phase 1:Session 基础设施

BuilderConfig.builderBootstrap()
  → ClawBootstrap.builder().cwd(home).model(model).build()
    → Builder.build() 内部:
      Phase 1: 构建 Session 基础设施
        ├─ 加载 agentscope.json → 获取所有 Agent 配置
        ├─ 构建 mainAgent Builder → 提取 SubagentEntry 列表
        ├─ 创建 WorkspaceManager(mainWorkspace)
        ├─ 创建 DefaultAgentManager(entries, wsManager)  ← 子 Agent 创建工厂
        ├─ 创建 SessionStore(sessions.json)  ← JSON 文件持久化
        ├─ 创建 SessionAgentManager(dam, config, registry, store)  ← 会话管理核心
        ├─ 创建 ChannelManager()  ← IM 通道注册表
        ├─ 创建 HarnessGateway(sam, channelMgr)  ← 消息路由网关
        ├─ 创建 WorkspaceTaskRepository(wsManager, main)
        ├─ 创建 SessionsTool(sam, taskRepo)  ← Agent 可调用的 sessions 工具
        └─ 创建 OutboundTool(channelMgr)  ← Agent 可调用的 outbound 工具

Phase 2:Agent 构建

Phase 2: 构建 Agent 实例
  for each agentId:
    ├─ 创建 HarnessAgent.Builder
    ├─ applyFileEntry() → 设置 name/sysPrompt/workspace/model 等属性
    ├─ 注入 SessionsTool(外部子 Agent 调用能力)
    ├─ 注入 OutboundTool(主动推送消息到 IM 通道能力)
    ├─ 应用 configureAllAgents → ToolNotificationMiddleware(工具调用事件推送)
    └─ b.build() → 生成 HarnessAgent 实例

Phase 3:Gateway 注册 + Channel 解析

Phase 3: 网关注册 + 通道解析
  for each (agentId, HarnessAgent):
    → gateway.registerAgent(agentId, agent)  ← 注册到路由表
  → gateway.bindMainAgent(mainAgent)  ← 标记主 Agent

  → resolveChannels() → 从 agentscope.json 的 channels 配置创建 Channel
    ├─ ChannelTypeRegistry 静态注册了 6 种类型:
    │   ├─ chatui   → ChatUiChannel (Web SSE)
    │   ├─ dingtalk  → DingTalkChannel
    │   ├─ wecom     → WeComChannel
    │   ├─ feishu    → FeishuChannel
    │   ├─ github    → GitHubChannel
    │   └─ gitlab    → GitLabChannel
    └─ 返回解析后的 Channel 列表

阶段 5:ClawHome 初始化 + Workspace 脚手架

BuilderConfig.builderBootstrap() 中,首次启动时会自动生成配置文件和 Workspace:

java
private void ensureAgentscopeConfig(Path home) throws IOException {
    Path configFile = home.resolve("agentscope.json");
    if (Files.exists(configFile)) return;  // 已存在则跳过

    // 创建目录结构
    Files.createDirectories(home);
    Files.createDirectories(home.resolve("workspace"));

    // 自动生成 agentscope.json
    String agentsJson = """
    {
      "main": "default",
      "agents": {
        "default": {
          "name": "claw",       ← 来自 claw.agent.name 配置
          "workspace": "workspace",
          "sysPrompt": "You are a helpful local assistant..."
        }
      }
    }
    """;
    Files.writeString(configFile, agentsJson);

    // 脚手架:创建 Workspace 默认文件结构
    WorkspaceScaffolder.scaffold(workspace, agentName, agentSysPrompt);
}

WorkspaceScaffolder 创建的默认 Workspace 结构:

~/.agentscope/claw/workspace/
  ├─ AGENTS.md              ← Agent 身份描述 + 系统提示词(从模板生成)
  ├─ tools.json             ← 工具白名单/黑名单配置
  ├─ skills/
  │   ├─ example-skill/
  │   │   └─ SKILL.md       ← 示例技能
  │   └─ (空目录,用户可添加技能)
  ├─ subagents/
  │   ├─ README.md          ← 子 Agent 说明
  │   └─ (空目录,用户可添加子 Agent)
  └─ memory/
      └─ .gitkeep            ← 空标记文件(让 git 能跟踪空目录)

AGENTS.md 模板:

markdown
# claw  ← {{NAME}} 占位符替换为 agent 名称

You are a helpful local assistant... ← {{SYSPROMPT}} 占位符替换

## How this folder works
  ... (使用说明)

重要:WorkspaceScaffolder 使用"写时跳过"语义(writeIfMissing),已存在的文件不会被覆盖,所以重复启动不会破坏用户修改。

阶段 6:Channel 启动

BuilderConfig.builderBootstrap() 返回前,会启动所有 Channel:

java
// 创建 ChatUiChannel
ChannelConfig chatuiCfg = ChannelConfig.builder(ChatUiChannel.CHANNEL_ID)
        .dmScope(DmScope.MAIN)    ← 单用户共享模式
        .build();
ChatUiChannel webChannel = ChatUiChannel.create(chatuiCfg);

// 注册文件配置中的 Channel(dingtalk/wecom 等)
for (Channel ch : bootstrap.registeredChannels()) {
    if (!ChatUiChannel.CHANNEL_ID.equals(ch.channelId())) {
        bootstrap.channelManager().register(ch);
    }
}

// 启动所有 Channel
bootstrap.start(webChannel);

DmScope.MAIN 是什么?

  • DmScope.MAIN — 所有对话共享一个会话(单用户模式)
  • DmScope.PER_PEER — 每个用户独立会话(Builder 使用这种)
  • PAW 使用 MAIN 因为它是单用户本地应用

阶段 7:Netty 服务器就绪

Spring Boot WebFlux 自动配置启动 Netty 服务器:

Netty Server started on port 8080
  → 监听所有 HTTP 请求
  → /api/* → REST Controller 处理
  → /actuator/* → Actuator 处理
  → 其他路径 → WebConfig SPA Fallback → 返回 index.html

此时应用完全就绪,可以接受浏览器请求。


四、ClawBootstrap 核心架构详解

4.1 ClawBootstrap 的两个角色

ClawBootstrap 有两个运行阶段:

  1. Build 阶段Builder.build())— 静态组装,创建所有对象和依赖关系
  2. Runtime 阶段start() / chatUiChannel())— 动态运行,启动 Channel 开始监听

4.2 Session 管理体系

PAW 的会话管理是一个多层级体系:

SessionAgentManager (核心)
  ├─ sessionsByKey (ConcurrentHashMap) ← 会话注册表
  │   sessionKey → SessionEntry (元数据)
  ├─ SessionStore (JSON 文件持久化)
  │   ~/.agentscope/claw/agents/{agentId}/sessions.json
  ├─ SubagentRunRegistry ← 运行跟踪
  ├─ AnnounceDispatcher ← 子 Agent 完成通知
  ├─ SpawnInterceptor ← 子 Agent 创建拦截
  └─ SessionTurnGate ← 会话级别公平锁(防止并发冲突)

会话类型SessionKind):

  • MAIN — 主 Agent 的对话会话
  • SUBAGENT — 子 Agent 的临时会话

4.3 HarnessGateway 路由机制

Gateway 是所有消息的路由中心:

ChatController.stream()
  → resolveRoute(agentId, message)  ← 通过 ChannelRouter 确定路由
    → resolveGatewayAgentId(agentId) ← 确定目标 Agent
      ├─ 内置 Agent → 直接返回 agentId
      └─ 自定义 Agent → 懒加载:首次调用才构建 HarnessAgent 并注册到 Gateway
  → gateway.run(context, messages, outboundAddress)
    → resolveOrCreateMainSession(gateKey, ha) ← 创建或复用会话
    → withGatedTurn(gateKey, () -> ha.call(messages, runtimeContext))
      → SessionTurnGate.acquire(gateKey) ← 加锁(同一会话串行执行)
      → HarnessAgent.call(messages, ctx)  ← Agent 执行
      → SessionTurnGate.release(gateKey) ← 释放锁

4.4 自定义 Agent 懒加载机制

PAW 支持用户通过 UI 创建自定义 Agent。这些 Agent 不会在启动时构建,而是 首次对话时才构建并注册

java
// AgentCatalogService.resolveGatewayAgentId()
public String resolveGatewayAgentId(String agentId) {
    if (isBuiltin(agentId)) {
        return agentId;  // 内置 Agent 直接返回
    }
    // 自定义 Agent → 懒加载
    return registeredCustomIds.computeIfAbsent(agentId, k -> buildAndRegisterCustom(entry));
}

private String buildAndRegisterCustom(StoredEntry entry) {
    HarnessAgent.Builder b = HarnessAgent.builder();
    b.name(name);
    b.sysPrompt(sysPrompt);
    b.workspace(workspace);
    b.middleware(new ToolNotificationMiddleware(toolEventBus));  // 工具事件推送
    if (entry.model() != null) b.model(entry.model());
    else if (model != null) b.model(model);  // 使用默认模型

    HarnessAgent agent = b.build();
    gateway.registerAgent(entry.id(), agent);  // 注册到路由表
    return entry.id();
}

这种设计的好处:

  • 启动快 — 不需要构建所有自定义 Agent
  • 节省资源 — 不常用的 Agent 不占内存
  • 动态更新 — 修改自定义 Agent 后只需清除缓存,下次对话自动重建

五、前端构建与 SPA 部署

5.1 前端构建流程

Maven generate-resources 阶段
  → frontend-maven-plugin
    ├─ install-node-and-npm  → 下载 Node v20.19.2 + npm 10.9.2
    ├─ npm install           → 安装 React/Vite 等依赖
    └─ npm run build         → Vite 编译
      → Vite 配置 outDir: ../src/main/resources/static
      → 生成 index.html + JS/CSS bundles
      → 输出到 src/main/resources/static/

5.2 SPA Fallback 机制

WebConfig 配置了 SPA 路由回退:

java
@Bean
public RouterFunction<ServerResponse> spaFallback() {
    return RouterFunctions.route()
            .GET(
                request -> !path.startsWith("/api")       // 非 API
                        && !path.contains(".")            // 非静态资源(无文件扩展名)
                        && !path.startsWith("/actuator"), // 非 Actuator
                request -> ServerResponse.ok()
                        .contentType(MediaType.TEXT_HTML)
                        .bodyValue(new ClassPathResource("/static/index.html")))
            .build();
}

浏览器请求流程

请求路径处理方式
/classpath:/static/index.html (React SPA)
/agents/123classpath:/static/index.html (React Router 处理)
/api/agentsAgentCatalogController (REST API)
/api/agents/default/chat/streamChatController (SSE 流式)
/static/assets/app.js→ Spring 默认静态资源处理器
/actuator/health→ Spring Actuator

六、聊天流程详解(端到端)

6.1 SSE 流式对话

浏览器发送: POST /api/agents/default/chat/stream
  body: { "message": "你好", "sessionKey": null }

ChatController.stream(agentId, req)
  ├─ handleSlashCommand() → 检查是否 /new /reset 命令
  ├─ resolveGateKey(agentId) → 确定路由键
  │   → resolveRoute(agentId, "__probe__")
  │     → catalogService.resolveGatewayAgentId("default")
  │     → ChannelRouter.resolveRoute(chatui.config(), inbound)
  │     → RouteResult { context, outboundAddress }
  ├─ findSessionKeyByGate(gateKey) → 查找已有会话
  ├─ toolEventBus.subscribe(sessionKey) → 监听工具调用事件
  ├─ executeChat(agentId, message) → Agent 执行
  │   → resolveRoute(agentId, message)
  │   → gateway.run(context, messages, outboundAddress)
  │     → resolveOrCreateMainSession(gateKey, ha) → 创建/复用会话
  │     → withGatedTurn → SessionTurnGate.acquire(gateKey)
  │     → HarnessAgent.call(messages, runtimeContext)
  │       → ReActAgent 执行循环:
  │         ├─ 调用 DashScope API → 生成回复或工具调用
  │         ├─ ToolNotificationMiddleware.onActing() → 发布 TOOL_CALL 事件到 ToolEventBus
  │         ├─ 执行工具 → 工具返回结果
  │         └─ 重复直到生成最终回复或达到 maxIters
  │     → SessionTurnGate.release(gateKey)
  │     → 返回 Msg (最终回复)
  └─ 合并 SSE 事件流:
    Flux.merge(toolEvents, agentReply)
      → SSE 事件序列:
        ├─ event: tool_call   data: {"type":"tool_call","toolName":"read_file",...}
        ├─ event: tool_result data: {"type":"tool_result","toolName":"read_file",...}
        ├─ event: token       data: {"type":"token","data":"你好!我是..."}
        └─ event: done        data: {"type":"done","sessionKey":"..."}

6.2 同步对话

浏览器发送: POST /api/agents/default/chat/send
  body: { "message": "你好", "sessionKey": null }

ChatController.send(agentId, req)
  → executeChat(agentId, message) → 同上,但返回完整回复而非 SSE 流
  → ChatResponse { reply: "你好!...", sessionKey: "..." }

6.3 Slash 命令

命令效果
/new清除当前会话历史,下次消息重新开始
/reset/new

七、ClawHome 目录结构详解

所有持久化状态都存储在 clawHome 目录下:

~/.agentscope/claw/                     ← clawHome (可通过 CLAW_HOME 环境变量修改)
  ├─ agentscope.json                     ← 内置 Agent 配置(启动时自动生成)
  │   {
  │     "main": "default",
  │     "agents": {
  │       "default": {
  │         "name": "claw",
  │         "workspace": "workspace",
  │         "sysPrompt": "..."
  │       }
  │     },
  │     "channels": { ... },             ← IM 通道配置(可选)
  │     "session": { ... }               ← 会话生命周期配置(可选)
  │   }
  ├─ agents.json                         ← 自定义 Agent 定义(用户通过 UI 创建)
  ├─ workspace/                          ← 主 Agent 的 Workspace
  │   ├─ AGENTS.md
  │   ├─ tools.json
  │   ├─ skills/
  │   ├─ subagents/
  │   └─ memory/
  ├─ agents/{agentId}/workspace/         ← 自定义 Agent 的 Workspace
  ├─ agents/{agentId}/sessions.json      ← 每个Agent的会话持久化文件
  └─ marketplace/                        ← 技能市场缓存

多应用隔离:每个 harness 应用有自己的 clawHome。Builder 使用 ~/.agentscope/builder,DataAgent 使用 ~/.agentscope/dataagent,PAW 使用 ~/.agentscope/claw,互不干扰。


八、ToolEventBus + ToolNotificationMiddleware 详解

PAW 有一个独特的事件推送机制,用于在 SSE 流中实时展示 Agent 的工具调用过程:

ToolNotificationMiddleware (Agent Middleware)
  → onActing() → 在每个工具调用前发布 TOOL_CALL 事件
  → ToolEventBus (Spring @Component, 内存事件总线)
    → Sinks.Many<ToolEvent> (Reactor 多播)
    → publish(ToolEvent) → 推送事件
    → subscribe(sessionKey) → 按 sessionKey 过滤订阅

ChatController.stream()
  → toolEventBus.subscribe(sessionKey) → 订阅工具事件
  → toToolFrame(event) → 转换为 SSE frame
  → 合并到 SSE 流中

这使浏览器能实时看到 Agent 正在调用什么工具,而不仅仅是最终文字回复。


九、Channel 体系详解

9.1 ChannelTypeRegistry

ChannelTypeRegistry 是 Channel 的工厂注册表,静态初始化了 6 种 Channel 类型:

java
static {
    register("chatui",   (id, routing, props) -> ChatUiChannel.create(routing));
    register("dingtalk", DingTalkChannel::fromProperties);
    register("wecom",    WeComChannel::fromProperties);
    register("feishu",   FeishuChannel::fromProperties);
    register("github",   GitHubChannel::fromProperties);
    register("gitlab",   GitLabChannel::fromProperties);
}

9.2 Channel 配置示例

agentscope.json 中可以配置 IM 通道:

json
{
  "channels": {
    "dingtalk": {
      "type": "dingtalk",
      "properties": {
        "appId": "...",
        "appSecret": "..."
      }
    },
    "feishu": {
      "type": "feishu",
      "disabled": true,
      "properties": { ... }
    }
  }
}
  • "disabled": true → 该 Channel 被跳过,不创建
  • "type" → 如果不指定,默认使用 channelId 作为 type(向后兼容)

十、配置参数速查表

10.1 YAML 配置(application.yml)

参数默认值说明
server.port8080HTTP 端口(CLAW_PORT 环境变量覆盖)
spring.application.nameagentscope-claw应用名称
claw.home~/.agentscope/claw数据根目录(CLAW_HOME 环境变量覆盖)
claw.dashscope.api-keyDashScope API Key(DASHSCOPE_API_KEY 环境变量覆盖)
claw.dashscope.model-nameqwen-maxDashScope 模型名(CLAW_MODEL_NAME 环境变量覆盖)
claw.dashscope.streamtrue是否启用流式输出
claw.agent.nameclaw默认 Agent 名称(CLAW_AGENT_NAME 环境变量覆盖)
claw.agent.sys-promptYou are a helpful local assistant...默认系统提示词

10.2 环境变量覆盖

环境变量对应 YAML说明
CLAW_PORTserver.port修改端口
CLAW_HOMEclaw.home修改数据目录
DASHSCOPE_API_KEYclaw.dashscope.api-keyDashScope API Key(启动必需
CLAW_MODEL_NAMEclaw.dashscope.model-name模型名称
CLAW_AGENT_NAMEclaw.agent.name默认 Agent 名称

10.3 agentscope.json 配置

字段类型说明
mainstring主 Agent ID(默认 "default"
agentsMap<string, AgentConfigEntry>Agent 定义列表
agents.{id}.namestringAgent 显示名称
agents.{id}.workspacestringWorkspace 路径(相对于 clawHome)
agents.{id}.sysPromptstring系统提示词
agents.{id}.modelstring模型名称(覆盖默认)
agents.{id}.maxItersint最大推理轮次
agents.{id}.toolsobject工具白名单/黑名单配置
agents.{id}.identityobjectAgent 身份配置(名称、头像)
agents.{id}.groupChatobject群聊配置
agents.{id}.sandboxobjectDocker 沙箱配置
agents.{id}.skillsobject技能白名单/黑名单
channelsMap<string, ChannelConfigEntry>IM 通道配置
sessionobject会话生命周期配置
session.maintenanceobject会话维护配置(清理策略)

十一、常见启动问题与解决

11.1 "Cannot start without a model"

IllegalStateException: agentscope-claw cannot start without a model.

原因:没有配置 DashScope API Key,也没有注册自定义 Model Bean。

解决

bash
# 方式 1:设置环境变量
set DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxx

# 方式 2:修改 application.yml
claw:
  dashscope:
    api-key: sk-xxxxxxxxxxxxxxxx

# 方式 3:自定义 Model Bean(如使用 OpenAI)
@Configuration
public class MyModelConfig {
    @Bean
    public Model model() {
        return OpenAIChatModel.builder().apiKey("...").modelName("gpt-4o").build();
    }
}

11.2 "BOM not found" / SNAPSHOT 依赖缺失

Non-resolvable import POM: io.agentscope:agentscope-bom:pom:2.0.0-SNAPSHOT (absent)

原因:在子目录单独运行 mvn exec:java,Maven 找不到本地仓库中的 SNAPSHOT 依赖。

解决:从根目录构建,利用 Maven Reactor:

bash
cd d:\InspurCode\micro\agentscope-java
mvn clean package -DskipTests -pl agentscope-examples/agents/agentscope-paw -am
java -jar agentscope-examples/agents/agentscope-paw/target/agentscope-paw-2.0.0-SNAPSHOT.jar

11.3 端口冲突

Web server failed to start. Port 8080 was already in use.

解决

bash
# 方式 1:修改端口
set CLAW_PORT=8081
java -jar agentscope-paw.jar

# 方式 2:找到占用进程并关闭
netstat -ano | findstr :8080
taskkill /PID <pid> /F

11.4 前端构建失败

npm ERR! code ERESOLVE ... (依赖冲突)

解决:清除 Node 缓存重新构建:

bash
cd agentscope-paw/frontend
rm -rf node_modules package-lock.json
cd agentscope-java
mvn clean package -DskipTests -pl agentscope-examples/agents/agentscope-paw -am

11.5 agentscope.json 自动生成问题

首次启动时 PAW 会自动生成 ~/.agentscope/claw/agentscope.json。如果你想重置:

bash
# 删除整个 clawHome(会清除所有会话和自定义 Agent)
rm -rf ~/.agentscope/claw

# 或仅删除配置文件
rm ~/.agentscope/claw/agentscope.json

十二、关键术语解释

术语解释
ClawBootstrapAgent 运行时的组装器,使用 Builder 模式构建所有核心组件(Agent、Gateway、Channel)
HarnessAgent带有工具调用、子 Agent、Workspace 的完整 Agent 实例
HarnessGateway消息路由网关,将外部请求路由到正确的 Agent,管理会话和并发
ChatUiChannelWeb UI 通道,处理 SSE 流式对话和会话管理
ChannelManager通道生命周期管理器,负责注册、初始化、启动和消息投递
ChannelTypeRegistryChannel 工厂注册表,按 type 字符串查找对应的创建工厂
SessionAgentManager会话管理核心,负责创建、追踪、维护、重置会话
SessionTurnGate会话级别的公平锁,确保同一会话的对话串行执行
SessionsToolAgent 可调用的工具,用于创建和调用子 Agent 会话
OutboundToolAgent 可调用的工具,用于主动推送消息到 IM 通道
ToolEventBus内存事件总线,将 Agent 的工具调用事件推送到前端 SSE 流
ToolNotificationMiddlewareAgent Middleware,在每次工具调用前发布事件到 ToolEventBus
DmScope.MAIN会话作用域:所有对话共享一个会话(单用户模式)
clawHome数据根目录(默认 ~/.agentscope/claw),存储所有配置、Workspace、会话
agentscope.json核心配置文件,定义内置 Agent、IM 通道、会话策略
WorkspaceAgent 的"文件系统",包含 AGENTS.md、tools.json、skills、subagents、memory
WorkspaceScaffolder首次启动时自动创建 Workspace 默认文件结构的工具

十三、启动流程总结(时序图)

时间线 →

[JVM 启动]
  → Spring Boot Launcher 解压依赖
  → 创建 ApplicationContext
  → 扫描 @SpringBootApplication → io.agentscope.claw2

[自动配置]
  → WebFlux AutoConfig → Netty 服务器 (port 8080)
  → Security AutoConfig → SecurityWebFilterChain (permitAll)
  → Actuator AutoConfig → /actuator/health, /actuator/info

[Bean 组装]
  → BuilderConfig.dashscopeModel() → DashScopeChatModel (条件创建)
  → BuilderConfig.builderBootstrap()
    → resolveClawHome() → ~/.agentscope/claw
    → ensureAgentscopeConfig() → 自动生成 agentscope.json + Workspace
    → ClawBootstrap.builder().cwd(home).model(model)
      → configureAllAgents → ToolNotificationMiddleware
      → .build()
        → [Phase 1] Session 基础设施
          → SessionStore + SessionAgentManager + HarnessGateway
          → SessionsTool + OutboundTool
        → [Phase 2] Agent 构建
          → 每个 agentId → HarnessAgent.Builder → build()
        → [Phase 3] Gateway 注册 + Channel 解析
          → gateway.registerAgent() × N
          → gateway.bindMainAgent()
          → resolveChannels() → ChannelTypeRegistry
    → ChatUiChannel.create(DmScope.MAIN)
    → 注册非 chatui Channel 到 ChannelManager
    → bootstrap.start(webChannel) → 启动所有 Channel

[Netty 就绪]
  → Netty Server started on port 8080
  → SPA Fallback → /static/index.html
  → REST API → /api/agents, /api/agents/{id}/chat/stream

[应用就绪] ← 浏览器访问 http://localhost:8080