Agent Teams 让多个 Claude Code 实例并行工作并通过共享任务列表相互协调。与 subagents 不同,agent teams 中的队友可以直接相互通信、协作和分享发现,适合需要团队协作的复杂任务。

本文将详细介绍 Agent Teams 的启用方式、使用方法、最佳实践以及与 Subagents 的区别。

一、Agent Teams 概述

1.1 什么是 Agent Teams

Agent Teams 是一组并行工作的 Claude Code 实例,通过共享任务列表协调工作。每个队友都是完整的、独立的 Claude Code 会话,拥有自己的 context window。

核心组件:

组件 角色
Team Lead 创建团队、生成队友并协调工作的主 Claude Code 会话
Teammates 各自处理分配任务的独立 Claude Code 实例
Task List 队友认领和完成的共享工作项列表
Mailbox 代理之间通信的消息系统

1.2 与 Subagents 的区别

Agent Teams 和 Subagents 都支持并行工作,但运作方式不同:

特性 Subagents Agent Teams
Context 自己的 context window;结果返回给调用者 自己的 context window;完全独立
通信 仅向主代理报告结果 队友直接相互发送消息
协调 主代理管理所有工作 共享任务列表,自我协调
最适合 只有结果重要的专注任务 需要讨论和协作的复杂工作
令牌成本 较低:结果汇总回主 context 较高:每个队友是独立的 Claude 实例
阅读全文 »

Subagents 是 Claude Code 中处理特定类型任务的专门 AI 助手。每个 subagent 在自己独立的 context window 中运行,具有自定义系统提示、特定的工具访问权限和独立的权限控制。当 Claude 遇到与 subagent 描述相匹配的任务时,它会委托给该 subagent,subagent 独立工作后返回结果。

本文将从创建方法、使用场景和最佳实践三个维度,全面介绍 Claude Code Sub-Agents 的使用。

一、如何创建 Sub-Agent

1.1 使用 /agents 命令(推荐方式)

/agents 命令提供了交互式界面来管理 subagents,这是推荐的创建方式:

1
/agents

在交互界面中可以:

  • 查看所有可用的 subagents(内置、用户、项目和插件)
  • 使用引导式设置或 Claude 生成创建新的 subagents
  • 编辑现有 subagent 配置和工具访问权限
  • 删除自定义 subagents

创建步骤示例:

  1. 选择 Create new agent

  2. 选择范围:User-level(用户级)或 Project-level(项目级)

  3. 选择 Generate with Claude 让 Claude 自动生成配置

  4. 描述你的 subagent,例如:

    1
    2
    A code improvement agent that scans files and suggests improvements
    for readability, performance, and best practices.
  5. 选择工具权限(只读审查者只需选择 Read-only tools)

  6. 选择模型(Sonnet 平衡能力和速度,Haiku 更快更便宜)

  7. 保存后立即可用,无需重启

阅读全文 »

一、基础概念

什么是 Skills?
Skills 是 Claude Code 的扩展机制,让你可以创建、管理和分享自定义能力。每个 Skill 是一个包含 SKILL.md 文件的目录,Claude 会将其添加到工具箱中。

核心价值:

  • 自动化重复任务
  • 添加领域专业知识
  • 强制执行代码标准和最佳实践
  • 加速项目脚手架和代码生成

二、文件结构

1
2
3
4
5
6
~/.claude/skills/<skill-name>/
├── SKILL.md # 必需,主指令文件
├── templates/ # 可选,模板文件
├── examples/ # 可选,示例输出
├── scripts/ # 可选,脚本文件
└── docs/ # 可选,详细参考文档

Skills 存储位置:

位置 路径 适用范围
Enterprise 托管设置 组织内所有用户
Personal ~/.claude/skills/ 所有项目
Project .claude/skills/ 当前项目
Plugin <plugin>/skills/ 插件启用范围

阅读全文 »

本文为基于 JaCoCo 源码二次开发的全流程方案,旨在实现类局部修改后保留未改动方法历史覆盖率,仅失效改动方法覆盖率的核心诉求,兼顾 JaCoCo 原生正确性原则与业务实用性。

一、核心目标与不可突破原则

1.1 核心开发目标

  • 类局部修改(方法 / 代码块修改)时,未改动方法的历史覆盖率完全复用,改动方法覆盖率清零并重新统计;
  • 完全兼容 JaCoCo 原生能力:插桩方式(动态 / 离线)、覆盖率维度、dump 文件格式、构建插件(Maven/Gradle);
  • 坚守正确性底线:仅当方法字节码逻辑完全未变时才复用覆盖率,杜绝错误数据。

1.2 不可突破原则

  • 正确性原则:复用数据必须基于方法逻辑一致性,方法体改动则覆盖率强制失效;
  • 向下兼容原则:改造后 JaCoCo 可解析原生 dump 文件,原生 JaCoCo 可解析改造后文件(降级兼容);
  • 无侵入原则:业务侧无感知,使用方式与原生 JaCoCo 一致,新增开关控制功能。

1.3 核心改造思想

原生 JaCoCo 以 类级别 ClassID 绑定覆盖率数据,类变更则全量失效;改造后下沉为 方法级别 MethodID 绑定,实现方法级粒度的失效与复用,核心逻辑如下:

  • 覆盖率数据存储结构从 ClassID + 全局探针ID → ClassID + MethodID + 方法内局部探针ID

阅读全文 »

最近使用 Guava 中 RateLimiter.getRate() 方法时遇到了一个反直觉的现象,这里贴出来给大家分享一下。

现象

我们先看下下面这段代码

1
2
3
4
5
public static void main(String[] args) {
RateLimiter rateLimiter = RateLimiter.create(10.0D);
double rate = rateLimiter.getRate();
System.out.println(rate);
}

直觉上最终会输出 10.0,实际最终也是输出了 10.0

可是如果我们将上述代码中的 10.0D 变为 15.0D,结果会输出什么呢?

1
2
3
4
5
public static void main(String[] args) {
RateLimiter rateLimiter = RateLimiter.create(15.0D);
double rate = rateLimiter.getRate();
System.out.println(rate);
}

执行后输出的值竟然是 14.999999999999998。看到这个结果我觉得非常的奇怪,毕竟直觉告诉我应该输出 15.0 才对。

刨根问底

看到这样的现象,我们首先就要从源码上分析一下,看一下 Guava 内部是如何处理的。

阅读全文 »

JaCoCo 简介

JaCoCo should provide the standard technology for code coverage analysis in Java VM based environments. The focus is providing a lightweight, flexible and well documented library for integration with various build and development tools.

JaCoCo 要为基于 Java VM 的环境中的代码提供覆盖率分析标准技术,重点是提供一个轻量、灵活且文档齐全的库,以便与各种构建和开发工具集成。

功能特性

  • 指令(C0)、分支(C1)、行、方法、类型、圈复杂度的覆盖率分析;
  • 基于 Java 字节码,即使没有源码依然可以运行;
  • 支持 on-the-fly 模式,可以通过 javaagent 便捷地集成,也可以通过 API 来自定义类装载器进行集成;
  • 与框架无关,可以对基于 Java VM 的应用程序顺利的集成;
  • 与已所有已发布的 Java 版本兼容;
  • 支持不同的 JVM 语言;
  • 可以生成多种格式的报告(HTML、XML、CSV);
  • 支持远程协议和 JMX 控制,可以在任何时间点来进行覆盖率数据的存储;

使用

下载完 JaCoCo 之后会得到几个 jar 包,其中 jacocoagent.jar 和 jacococli.jar 是我们比较关注的。

  • jacocoagent.jar 是 JVM 应用程序启动时的代理
  • jacococli.jar 是生成覆盖率报告、合并 dump 文件的命令行工具

jacocoagent

Java Agent 的原理可以阅读 java.lang.instrument.Instrumentation 这个类,这里先介绍 jacocoagent 的使用方法。

阅读全文 »

最近在做基于 JaCoCo 的代码覆盖率工具,了解到了一下 javaagent 相关的一些知识点。

javaagent 是什么?

javaagent 是 java 命令的一个参数选项,可以用于加载 Java 语言的代理。

那么这个参数加载的 Java 语言的代理需要满足什么样的规范呢?

  1. 代理 jar 包中的 MANIFEST.MF 文件需指定 Premain-Class;
  2. Premain-Class 指定的类中必须实现 premain 方法。

zsh 可能是目前最好用的终端。

安装

Mac OS

macOS 自带 zsh,并且当前最新版本 macOS 默认终端即为 zsh

Ubuntu

使用 sudo apt install zsh 命令即可安装 zsh

CentOS

使用 sudo yum install zsh 命令即可安装 zsh

在安装好 zsh 之后,第一次使用的时候,会有如下的提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
This is the Z Shell configuration function for new users,
zsh-newuser-install.
You are seeing this message because you have no zsh startup files
(the files .zshenv, .zprofile, .zshrc, .zlogin in the directory
~). This function can help you with a few settings that should
make your use of the shell easier.

You can:

(q) Quit and do nothing. The function will be run again next time.

(0) Exit, creating the file ~/.zshrc containing just a comment.
That will prevent this function being run again.

(1) Continue to the main menu.

(2) Populate your ~/.zshrc with the configuration recommended
by the system administrator and exit (you will need to edit
the file by hand, if so desired).

--- Type one of the keys in parentheses ---
阅读全文 »

使用 Spring 做项目的先思考一个问题,一个接口有在多个实现类的情况下,在成员变量的声明上如果没有指定注入那个别名的 Bean 的时候 Spring 会如何选择对应的 Bean 来进行注入?

问题模拟

先来简单说明一下示例代码。

定义一个接口,用于打印当前注入的 Bean 的名称

1
2
3
public interface TestService {
void printBeanName();
}

针对上面定义的接口编写两个实现类,并分别定义其 beanName 为 testService 和 testService2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service("testService")
public class TestServiceImpl implements TestService {

@Override
public void printBeanName() {
System.out.println("testService");
}
}

@Service("testService2")
public class TestServiceImpl2 implements TestService {

@Override
public void printBeanName() {
System.out.println("testService2");
}
}

编写 Handler,并注入 TestService

1
2
3
4
5
6
7
8
9
10
@Service
public class TestHandler {

@Autowired
private TestService testService;

public void test() {
testService.printBeanName();
}
}

编写单元测试,并观察输出

阅读全文 »

线上业务使用了 Guava 中的 RateLimiter 来做单机的限流。最近运维同事反馈,在修改了限流的配置之后,请求频率没有超过限流的配置却触发了限流逻辑,在此对该问题进行一下分析与总结。

问题模拟

根据上述问题的现象,初步推测问题可能跟 Guava 中 RateLimiter 的限流算法实现有关,为了方便排查问题,参考线上业务的限流的逻辑写了一个 demo,其核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@RestController
@RequestMapping("/hello")
public class HelloController {

/** 用于存储不同的接口,对应的 RateLimiter **/
private static Map<String, RateLimiter> RATE_LIMITER_MAP = Maps.newConcurrentMap();
/** 用于存储不同接口限流配置的参数 **/
private static Map<String, String> RATE_MAP = Maps.newConcurrentMap();

static {
// 初始化一个接口默认的限流配置
RATE_MAP.put("loan", "100000");
}

/**
* 测试限流的接口
* @param key 限流配置的 key
*/
@GetMapping("/testLimit")
public String testLimit(String key) {
String value = RATE_MAP.get(key);
logger.info("自定义限流配置={}", value);
Double currentRate = Double.parseDouble(value);

RateLimiter rateLimiter = RATE_LIMITER_MAP.get(key);
if (rateLimiter == null || !isSameRate(currentRate, rateLimiter.getRate())) {
logger.info("创建新的RateLimiter|速率={}", currentRate.toString());
rateLimiter = RateLimiter.create(currentRate);
RATE_LIMITER_MAP.putIfAbsent(key, rateLimiter);
}
boolean allowed = rateLimiter.tryAcquire();
logger.info("是否允许当前请求通过={}|rate={}", allowed, rateLimiter.getRate());
return allowed ? "SUCCESS" : "FAIL";
}

/**
* 模拟修改限流配置
* @param key 限流配置的key
* @param rate 限流的值
*/
@GetMapping("/setRate")
public String setRate(String key, String rate) {
RATE_MAP.put(key, rate);
String currentValue = RATE_MAP.get(key);
logger.info("设置完之后的值={}", currentValue);
return "SUCCESS";
}

}

启动工程,我们模拟一下出现问题时的操作。

  1. 构造 HTTP 请求调用 /setRate 接口,将 loan 对应的限流的值修改为 5;
  2. 构造 HTTP 请求快速调用 /testLimit 接口 2 次,发现第一次调用接口返回 SUCCESS,第二次接口返回 FAIL,说明第 2 次请求触发了限流;
  3. 构造 HTTP 请求调用 /testLimit 5 次,保证 5 次请求在 1 秒内接收,发现这 5 次请求接口均返回 SUCCESS,说明这 5 次请求均未触发限流;
  4. 构造 HTTP 请求调用 /testLimit 6 次,保证 6 次请求均在 1 秒内接收,发现前 6 次请求接口均返回 SUCCESS,说明这 6 次请求均未触发限流;
  5. 构造 HTTP 请求调用 /testLimit 7 次,保证 7 次请求均在 1 秒内接收,发现前 6 次请求接口均返回 SUCCESS,第 7 次请求接口返回 FAIL,说明前 6 次请求均未触发限流,第 7 次请求触发限流。

至此,我们模拟的现象与线上一致。步骤 3 是我们预期的结果,步骤 2 中在未超过限流配置的情况下触发了限流,步骤 4、5 中的第 6 次请求在超过了限流配置的情况下,仍然允许通过,我们直观上认为这些是不合理的。


排查与分析

Guava 中 RateLimiter 提供了两种令牌桶的算法实现:

阅读全文 »