阅读视图

发现新文章,点击刷新页面。

HTTPS超文本传输安全协议全面解析与工作原理

计算机网络---https(超文本传输安全协议)

1. HTTPS的定义与核心定位

HTTPS(HyperText Transfer Protocol Secure,超文本传输安全协议)并非独立于HTTP的“新协议”,而是 HTTP协议与TLS/SSL加密层的结合体——它在HTTP的应用层与TCP传输层(或HTTP/3的QUIC协议层)之间,增加了一套标准化的加密、认证与数据校验机制,核心目标是解决HTTP协议的安全缺陷,保障客户端与服务器之间的通信安全。

从技术本质来看,HTTPS的定位可概括为:

  • 基础不变:仍基于HTTP的请求/响应模型(方法、状态码、头字段完全兼容HTTP),底层传输依赖TCP(HTTP/1.x/2)或UDP(HTTP/3+QUIC);
  • 安全增强:通过TLS/SSL协议实现“加密传输+身份认证+数据防篡改”,弥补HTTP明文传输、无身份校验、数据易被劫持的短板;
  • 行业标配:当前所有涉及隐私(如登录、支付)、敏感数据(如政务、医疗)的场景均强制要求使用HTTPS,主流浏览器(Chrome、Safari)已对HTTP明文网站标记“不安全”警示。

2. HTTPS的核心价值:解决HTTP的三大安全缺陷

要理解HTTPS的必要性,需先明确HTTP的安全漏洞——这些漏洞在公网(如WiFi、运营商网络)中极易被利用,引发隐私泄露、财产损失等风险。HTTPS通过三大核心能力,针对性解决这些问题:

2.1 机密性(Confidentiality):防止数据被窃听

HTTP的致命缺陷是 明文传输:请求/响应内容(如密码、银行卡号、搜索记录)在网络中以“可读文本”形式传输,任何处于传输链路中的设备(如路由器、黑客的抓包工具)都能直接窃取数据。

例如,用户在HTTP网站输入“用户名:test,密码:123456”,抓包工具可直接捕获如下明文:

POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=test&password=123456

HTTPS通过 对称加密算法(如AES-256-GCM)解决此问题:客户端与服务器协商出一个“会话密钥”,所有数据传输前用该密钥加密,传输后用同一密钥解密——即使数据被截获,攻击者因无会话密钥,也无法还原为可读文本。

在开发或测试过程中,可以使用抓包工具如 Sniffmaster 来监控和分析 HTTPS 流量,以验证加密效果和排查问题,支持全平台操作且无需复杂代理设置。

2.2 完整性(Integrity):防止数据被篡改

HTTP不校验数据完整性:攻击者可通过“中间人攻击”(MITM)拦截HTTP数据包,修改内容后再转发给目标方,且双方均无法察觉。

典型场景:用户在HTTP电商网站下单,支付金额为100元,攻击者拦截请求后将金额改为10000元,服务器接收并处理修改后的请求,导致用户多支付。

HTTPS通过 哈希算法+数字签名 保证完整性:

  1. 发送方(如服务器)对数据计算哈希值(如SHA-256),生成“数据指纹”;
  2. 用私钥对哈希值签名,生成“数字签名”,与数据一同发送;
  3. 接收方(如客户端)用公钥验证签名,若验证通过,再对接收数据重新计算哈希值;
  4. 对比两次哈希值:一致则数据未被篡改,不一致则数据已被修改,直接丢弃。

2.3 身份认证(Authentication):防止身份被伪造

HTTP无身份认证机制:攻击者可伪造服务器(如搭建虚假WiFi热点,伪装成“商场免费WiFi”),诱骗用户访问虚假网站,窃取用户信息(即“钓鱼攻击”)。

例如,用户想访问 http://www.bank.com,但攻击者伪造了一个域名相似的 http://www.bankk.com,页面样式与真实银行完全一致,用户输入账号密码后,数据直接发送给攻击者。

HTTPS通过 CA证书体系 实现身份认证:服务器需向权威的“证书颁发机构(CA)”申请证书,证书中包含服务器的域名、公钥、CA签名等信息。客户端(如浏览器)接收证书后,会通过内置的“根CA证书”验证服务器证书的合法性——只有验证通过,才确认服务器是真实的,而非伪造。

3. HTTPS的关键组件:TLS/SSL协议与CA证书体系

HTTPS的安全能力依赖两大核心组件:TLS/SSL协议(负责加密与握手)和CA证书(负责身份认证),二者协同工作,构成HTTPS的安全基石。

3.1 TLS/SSL协议:HTTPS的“加密引擎”

TLS(Transport Layer Security,传输层安全协议)是SSL(Secure Sockets Layer)的升级版,当前主流版本为 TLS 1.2TLS 1.3(SSLv3因安全漏洞已被禁用)。TLS协议栈分为三层,自上而下分别为:

协议层 核心功能 关键技术/字段
警报层(Alert) 传递TLS会话中的错误信息(如证书过期、加密套件不支持),触发连接关闭或重试 警报级别(Warning/Fatal)、错误代码(如42表示证书过期)
握手层(Handshake) 协商TLS会话参数(加密套件、会话密钥)、交换证书、验证身份 客户端Hello、服务器Hello、证书、密钥交换消息
记录层(Record) 对应用层数据(HTTP请求/响应)进行分片、压缩、加密、添加认证标签 对称加密算法(AES)、哈希算法(SHA-256)、记录长度
TLS版本演进与核心优化

TLS协议的迭代始终围绕“更安全、更高效”展开,各版本的关键差异如下:

版本 发布时间 核心特点 安全性/性能评价
SSLv3 1996年 首个广泛应用的版本,但存在POODLE漏洞(可被破解加密) 已废弃,完全不安全
TLS 1.0 1999年 修复SSLv3漏洞,引入RSA密钥交换、AES加密 安全性不足(如BEAST漏洞),部分浏览器已禁用
TLS 1.1 2006年 修复BEAST漏洞,改进IV(初始化向量)生成方式 安全性一般,逐步被淘汰
TLS 1.2 2008年 支持SHA-2哈希算法、AEAD加密模式(如AES-GCM),增强完整性与机密性 当前主流版本,安全性可靠
TLS 1.3 2018年 1. 简化握手流程(从4次交互减至3次,支持0-RTT);
2. 移除不安全加密套件;
3. 合并密钥交换与服务器Hello
性能最优、安全性最高,逐步普及

关键优化:TLS 1.3的“0-RTT(Round-Trip Time)”握手可实现“首次重连时无需等待握手完成即可发送数据”,大幅降低延迟——例如,用户第二次访问某网站时,可直接用缓存的会话参数发送请求,无需重新协商密钥。

3.2 CA证书体系:HTTPS的“身份身份证”

CA(Certificate Authority,证书颁发机构)是公认的“网络信任第三方”,负责验证服务器身份并颁发证书。CA证书体系采用“层级信任模型”,确保证书的合法性可追溯,核心构成如下:

证书的核心结构(X.509标准)

一份合法的TLS证书包含以下关键信息(可通过浏览器“查看证书”功能查看):

  • 版本:如X.509 v3;
  • 序列号:CA分配的唯一标识,用于吊销证书;
  • 主体(Subject):证书持有者信息(服务器域名、公司名称等);
  • 公钥信息:服务器的公钥(用于加密会话密钥)及算法(如RSA、ECC);
  • 签发者(Issuer):颁发证书的CA名称(如Let’s Encrypt、Symantec);
  • 有效期:证书生效与过期时间(通常为1-2年,需提前续期);
  • 数字签名:CA用自身私钥对证书内容的哈希值签名,用于客户端验证。
证书的信任链验证

客户端(如浏览器)验证服务器证书时,需通过“信任链”逐层校验,确保证书未被伪造,流程如下:

  1. 客户端接收服务器证书,先验证“服务器证书的签名”——用CA的公钥解密签名,得到哈希值,再对证书内容重新计算哈希值,对比一致则证书内容未被篡改;
  2. 若签发服务器证书的是“中间CA”(而非根CA),则需验证“中间CA证书”的签名,直到追溯至“根CA证书”;
  3. 根CA证书是浏览器/操作系统内置的“信任根”(如微软根CA、苹果根CA),无需额外验证——若根CA未被信任(如自制证书),浏览器会弹出“证书风险”警示,阻止用户访问。
证书的类型与应用场景

根据验证严格程度,CA证书分为三类,适用于不同场景:

  • 域名验证型证书(DV证书):仅验证域名所有权(如通过DNS解析、文件上传验证),无公司信息,仅显示“安全锁”图标,适合个人博客、小型网站,免费(如Let’s Encrypt);
  • 组织验证型证书(OV证书):验证域名所有权+公司主体信息(如营业执照),证书中包含公司名称,适合企业官网、普通电商,需付费(年费数百至数千元);
  • 扩展验证型证书(EV证书):最高级别验证,需提交公司资质、法律文件、实地核验,浏览器地址栏会显示“绿色锁+公司名称”,适合金融、支付、政务网站,费用较高(年费数千元至数万元)。

4. HTTPS的核心工作流程:TLS握手与加密通信

HTTPS的通信过程分为两大阶段: TLS握手阶段(协商会话参数、交换证书、生成会话密钥)和 加密通信阶段(用会话密钥传输HTTP数据)。以下以主流的 TLS 1.2 和优化后的 TLS 1.3 为例,详细拆解流程。

4.1 TLS 1.2握手流程(4次交互)

TLS 1.2握手需客户端与服务器进行4次TCP交互(2个RTT),流程如下:

  1. 客户端Hello(Client Hello)
    • 客户端向服务器发送:支持的TLS版本(如TLS 1.2)、支持的加密套件(如TLS_RSA_WITH_AES_256_GCM_SHA384)、客户端随机数(Client Random,用于后续生成会话密钥)、会话ID(若为重连,携带历史会话ID)。
  2. 服务器Hello + 证书 + 服务器Hello Done
    • 服务器响应:确认TLS版本和加密套件(从客户端支持的列表中选择)、服务器随机数(Server Random,与Client Random共同用于生成密钥)、服务器证书(包含公钥)、“服务器Hello Done”消息(表示服务器已完成初始响应)。
  3. 客户端证书验证 + 密钥交换 + 客户端完成
    • 客户端验证服务器证书:通过信任链校验证书合法性,若验证失败,终止连接并提示风险;
    • 生成预主密钥(Pre-Master Secret):客户端生成一个随机数,用服务器证书中的公钥加密,发送给服务器(即“密钥交换消息”);
    • 生成会话密钥:客户端用Client Random + Server Random + Pre-Master Secret,通过PRF(伪随机函数)生成“会话密钥”(用于后续对称加密);
    • 发送“客户端完成”消息:包含对前序所有握手消息的哈希值+数字签名,证明握手过程未被篡改,同时告知服务器后续将用会话密钥加密数据。
  4. 服务器密钥交换 + 服务器完成
    • 服务器用自身私钥解密“预主密钥”,得到Pre-Master Secret;
    • 用与客户端相同的算法(Client Random + Server Random + Pre-Master Secret)生成会话密钥;
    • 发送“服务器完成”消息:包含对前序握手消息的哈希值+数字签名,告知客户端后续将用会话密钥加密数据。

至此,TLS握手完成,客户端与服务器持有相同的会话密钥,进入加密通信阶段。

4.2 TLS 1.3握手流程(3次交互,优化版)

TLS 1.3通过合并消息、减少交互,将握手压缩至3次TCP交互(1个RTT),流程如下:

  1. 客户端Hello + 密钥交换
    • 客户端发送:支持的TLS 1.3版本、加密套件(仅保留安全套件,如TLS_AES_256_GCM_SHA384)、客户端随机数、“密钥共享”消息(提前生成密钥交换所需的公钥参数,替代TLS 1.2的Pre-Master Secret)。
  2. 服务器Hello + 证书 + 密钥交换 + 服务器完成
    • 服务器响应:确认TLS 1.3版本和加密套件、服务器随机数、服务器证书、“密钥共享”消息(用客户端的公钥参数计算出会话密钥)、“服务器完成”消息(包含握手消息的哈希签名)。
  3. 客户端完成
    • 客户端验证证书后,用服务器的密钥共享参数生成会话密钥;
    • 发送“客户端完成”消息(包含握手消息的哈希签名),进入加密通信阶段。

核心优化:TLS 1.3删除了“服务器Hello Done”消息,将密钥交换与服务器响应合并,减少1次交互;同时支持“0-RTT”模式——若客户端缓存了历史会话参数(如会话票证),首次重连时可直接发送加密的HTTP请求,无需等待握手完成,进一步降低延迟。

4.3 加密通信阶段:HTTP数据的安全传输

TLS握手完成后,客户端与服务器开始用“会话密钥”传输HTTP数据,流程如下:

  1. 应用层(HTTP)生成请求/响应数据(如 GET /index.html HTTP/1.1);
  2. TLS记录层对数据进行处理:
    • 分片:将数据分割为最大16KB的记录;
    • 压缩(可选):用指定算法(如DEFLATE)压缩数据;
    • 加密:用会话密钥通过对称加密算法(如AES-GCM)加密数据;
    • 认证:添加“消息认证码(MAC)”或“认证标签(Tag)”,用于验证数据完整性;
  3. 加密后的记录通过TCP(或QUIC)传输给对方;
  4. 接收方的TLS记录层解密数据、验证完整性、解压缩、重组,再传递给应用层(HTTP)处理。

5. HTTPS的性能优化:打破“HTTPS更慢”的误区

早期HTTPS因TLS握手延迟、加密计算开销,确实比HTTP慢,但随着协议优化(如TLS 1.3)、硬件升级(CPU支持AES指令集)、缓存机制改进,HTTPS的性能已接近HTTP,甚至通过优化可超越HTTP。以下是核心优化方向:

5.1 减少TLS握手延迟

  • 升级TLS 1.3:从2个RTT减至1个RTT,重连时支持0-RTT,延迟降低50%以上;
  • 会话复用
    • 会话ID复用:服务器存储会话参数(如会话密钥),客户端重连时携带会话ID,无需重新协商密钥;
    • 会话票证(TLS Ticket)复用:服务器用密钥加密会话参数,生成“会话票证”发送给客户端,客户端重连时携带票证,服务器解密后直接复用会话,无需存储会话状态(适合分布式服务器);
  • OCSP Stapling(证书状态 stapling):客户端验证证书时,无需向CA服务器查询证书状态(如是否吊销),而是由服务器提前获取CA的OCSP响应,“装订”在证书中一同发送,减少1次CA查询的RTT。

5.2 降低加密计算开销

  • 选择高效加密套件:优先使用AEAD模式的加密套件(如AES-GCM、ChaCha20-Poly1305),兼顾安全与性能——ChaCha20在不支持AES指令集的设备(如低端手机)上性能比AES快3倍;
  • 采用ECC椭圆曲线加密:ECC(Elliptic Curve Cryptography)比RSA更高效,相同安全级别下,ECC的密钥长度更短(如256位ECC≈3072位RSA),加密/解密速度更快,适合移动设备和高并发场景。

5.3 优化证书传输

  • 证书链合并:将服务器证书、中间CA证书合并为一个文件,减少证书传输的TCP连接次数;
  • 证书压缩:使用Brotli或GZIP压缩证书(尤其是EV证书,内容较大),减少传输带宽。

5.4 结合HTTP/3与QUIC

HTTP/3基于QUIC协议(UDP传输),QUIC内置TLS 1.3加密,无需单独的TLS握手——QUIC的“0-RTT”握手可同时完成QUIC连接建立与TLS加密协商,进一步降低延迟;同时QUIC支持“连接迁移”(如手机从WiFi切换到4G,无需重新握手),提升移动场景下的HTTPS体验。

6. HTTPS的常见误区与澄清

误区1:“HTTPS绝对安全,不会被攻击”

HTTPS并非“绝对安全”,仍存在潜在风险,但需满足特定条件:

  • 证书私钥泄露:若服务器私钥被窃取,攻击者可伪造证书,拦截加密数据;
  • 弱加密套件:使用TLS 1.0/1.1或不安全套件(如TLS_RSA_WITH_3DES_EDE_CBC_SHA),可能被破解;
  • 中间人攻击(MITM):若用户信任了伪造的根CA证书(如恶意软件植入伪造根CA),攻击者可拦截并解密HTTPS数据。

应对措施:定期轮换私钥、禁用弱TLS版本和套件、通过安全工具(如Qualys SSL Labs)检测证书配置。此外,工具如 Sniffmaster 提供 HTTPS 抓包功能,支持无需代理的设置,便于开发者进行安全审计和调试。

误区2:“免费CA证书不安全,不如付费证书”

免费证书(如Let’s Encrypt)与付费证书的核心安全机制完全一致,均符合TLS标准,差异仅在验证严格程度和品牌信任度:

  • 安全层面:免费DV证书与付费OV/EV证书均采用相同的加密算法(如AES-256),均可实现机密性、完整性、身份认证;
  • 差异层面:付费证书验证公司信息,适合需要展示企业可信度的场景(如金融),免费证书适合个人或无需展示企业信息的场景(如博客)。

误区3:“HTTPS会增加服务器负载,不适合高并发”

早期HTTPS的加密计算确实会增加服务器CPU负载(约10%-20%),但通过优化可大幅缓解:

  • 硬件优化:使用支持AES-NI指令集的CPU(如Intel Xeon、AMD EPYC),加密计算速度提升10倍以上;
  • 软件优化:Nginx、Apache等服务器已优化TLS处理,支持多进程/多线程并发;
  • 缓存与会话复用:通过会话票证、OCSP Stapling减少重复计算,降低负载。

当前主流互联网公司(如阿里、腾讯)的高并发业务(秒杀、直播)均使用HTTPS,证明其可支撑高并发场景。


HTTPS不仅是“安全协议”,更是当前Web生态的“基础设施”——它通过TLS加密解决了HTTP的安全漏洞,保障了用户隐私与数据安全;同时,HTTPS也是SEO(搜索引擎优化)、PWA(渐进式Web应用)、WebSocket等技术的前提条件,无HTTPS则无法使用这些功能。

未来,HTTPS的发展方向将聚焦于:

  1. TLS 1.3的全面普及:浏览器与服务器逐步淘汰TLS 1.2及以下版本,强制使用更安全、更高效的TLS 1.3;
  2. 量子抗性加密(Post-Quantum Cryptography):研发可抵御量子计算攻击的加密算法(如格密码、哈希签名),避免未来量子计算机破解RSA/ECC加密;
  3. 简化证书管理:通过自动化工具(如Certbot)实现证书的自动申请、续期、部署,降低中小企业使用HTTPS的门槛。

连载04-CLAUDE.md ---一起吃透 Claude Code,告别 AI coding 迷茫

CLAUDE.md 完整指南——让 Claude 真正理解你的项目

AI Coding 系列第 04 篇 · CLAUDE.md 到底是什么:不是文档,而是 Claude 的规则层


CLAUDE.md 被严重误解

很多人对 CLAUDE.md 的理解有偏差。有人把它当项目文档来写,两百行的架构介绍、API 清单、数据库设计,然后疑惑为什么 Claude 经常无视其中的规则。有人复制了一个通用模板,放在那里从来不改。还有人干脆不知道它到底是干什么的。

这类误解有一个共同点:
CLAUDE.md 当成了“给 AI 看的项目说明书”。

CLAUDE.md 的本质不是文档,而是规则层

它不是用来完整介绍项目的,而是用来告诉 Claude:

  • 这个项目里哪些边界不能碰
  • 哪些行为默认是错的
  • 哪些约定会反复影响决策
  • 哪些高风险区域必须更保守

如果把它写成“项目背景”,Claude 最多只是“看过了”;
如果把它写成“行为规则”,Claude 的默认工作方式才会真正改变。

所以更准确的定义是:

CLAUDE.md 不是项目文档,而是把稳定偏好、高风险边界和重复纠正,提前变成 Claude 默认上下文的规则层。


一、CLAUDE.md 到底解决什么问题

一个好用的 CLAUDE.md,主要解决四类问题。

1. 把反复提醒的内容沉淀下来

如果你总是在 prompt 里反复说这些话:

  • 这个项目不要改 .github/workflows
  • 错误统一用 AppError
  • 不要默认新增依赖
  • 数据库变更前先讲回滚方案

那这些内容就不该每次重新说,而应该进入 CLAUDE.md

2. 给 Claude 的默认积极性加边界

Claude 默认会尽量帮你完成任务,但很多项目里真正危险的,不是它不做事,而是它做得太多。

比如:

  • 看见旧代码就想顺手重构
  • 看见没测试就想补一整套基础设施
  • 看见当前实现笨重就建议换栈

这些行为在通用场景里未必错,但在具体项目里可能是噪音,甚至是风险。
CLAUDE.md 的一个重要作用,就是给这种默认积极性划边界。

3. 把“代码里看不出来”的规则显式化

很多项目真正重要的约束,并不直接写在代码里。

例如:

  • 某个目录是历史包袱区,轻易别碰
  • 某些 migration 一旦上线后绝不能回写修改
  • 某个模块表面简单,背后连着外部系统
  • 某类接口一改就会影响前端联调和埋点

这些东西人类同事待久了会知道,但 AI 初来乍到不会知道。
CLAUDE.md 的价值,就在于把这些隐性知识提前说透。

4. 降低上下文成本

技术栈、关键路径、错误处理方式、依赖策略、部署边界,这些稳定规则本来就适合长期存在。把它们放进 CLAUDE.md,每次 prompt 就能专注当前任务,而不是重复灌输基础背景。


二、它不只是纠错层,也是预防层

前面说 CLAUDE.md 是纠偏器,这个说法是对的,而且很重要。因为它能一下子把很多人从“项目文档思维”拉回来。

但如果只停在“纠偏器”这一层,对它的理解还是不完整。

更准确地说,CLAUDE.md 既是纠错层,也是预防层

1. 纠错层:把重复犯的错写成规则

比如:

  • 你已经说过两次不要直接 throw new Error()
  • 你已经纠正过几次不要改 .github/workflows
  • 你已经反复提醒过不要随便 npm install

这些都属于典型的“纠错”。

2. 预防层:提前声明高代价边界

真正好用的 CLAUDE.md,并不只是在事后补锅。它还有一个同样重要的作用:提前声明那些一旦做错,代价就很高的边界。

比如:

  • 支付模块改动前先确认幂等逻辑
  • migration 文件上线后只能新增,不能回写修改
  • 生成目录不要手改,因为下次生成会覆盖
  • 新增重大依赖前先说明必要性和替代方案

这些规则不一定是 Claude 已经犯过的错,也可能是你提前告诉它:

“这里不是不能动,而是这里的错误成本很高,你默认要更保守。”

所以从完整定位上说,CLAUDE.md 的作用不是单纯“记录反复犯的错”,而是:

把稳定偏好、风险边界和高代价约束,提前变成 Claude 的默认工作上下文。

CLAUDE.md 的真实定位

图:CLAUDE.md 不是项目文档,而是纠偏层 + 预防层 + 长期约束层。


三、文档式写法 vs 纠偏式写法

说一百遍不如直接对比。

❌ 文档式写法(Claude 读了,但行为不变)

本项目是一个电商平台,使用 Node.js + Express + TypeScript 开发,
数据库采用 PostgreSQL,通过 Prisma 进行 ORM 管理。
项目包含用户模块、订单模块、支付模块和通知模块,
遵循 RESTful API 设计规范……
✅ 纠偏式写法(Claude 读了,行为立刻改变)

- 禁止 throw new Error(),统一用 AppError 类
- API 响应必须含 success / data / timestamp 三个字段,不能自己发明格式
- 禁止在 controller 层直接写 SQL,必须通过 service 层
- 所有异步函数必须有 try-catch,不靠外层中间件兜底
- 新增依赖前必须问我,不要自行 npm install

文档式写法让 Claude “知道”了,但知道不等于行动。
纠偏式写法告诉 Claude:“在这个项目里,你的默认行为哪里不对。” 这才是它真正听进去的语言。

判断一条规则是不是纠偏式,只用问一个问题:

这条规则是在纠正 Claude 的某个具体行为,还是在描述项目背景?

能对应到一个具体行为变化的,是纠偏。
其他的,是文档。


四、它和 Prompt、文档、Memory、Skill 的边界

很多人用不好 CLAUDE.md,不是不会写规则,而是把它和别的东西混在一起了。

最容易混淆的有四个对象:Prompt、项目文档、Memory、Skill。

CLAUDE.md 和其他机制的边界

图:Prompt 管当前任务,文档管背景,Memory 管自动沉淀,Skill 管重复流程,CLAUDE.md 管稳定规则。

源码里的分工也很明确

如果去看 Claude Code 的源码,CLAUDE.mdMemory 的边界其实分得很清楚。相关实现可以看 src/utils/claudemd.ts。在这部分实现里,CLAUDE.md 被归在一套明确的 instruction loading 顺序里:

  1. Managed memory:全局托管规则
  2. User memory:~/.claude/CLAUDE.md
  3. Project memory:仓库里的 CLAUDE.md.claude/CLAUDE.md.claude/rules/*.md
  4. Local memory:CLAUDE.local.md

这套机制本质上是在加载指令文件

而同一个文件里又能看到另一套独立机制:当 auto memory 打开时,系统会额外读取 getAutoMemEntrypoint() 返回的 MEMORY.md,其类型是 AutoMem,团队记忆则是 TeamMem。源码里甚至专门写了注释:

AutoMem/TeamMem are intentionally excluded — they're a separate memory system, not "instructions" in the CLAUDE.md/rules sense.

这句话非常关键。它说明:

  • CLAUDE.md 这一层,本质上是 instructions / rules
  • MEMORY.md 这一层,本质上是 auto memory / persistent memory

它们最后都会进入上下文,但在架构里并不是同一个东西。

所以如果从源码上更严格地说,CLAUDE.md 不是 MEMORY.md 的别名,更不是 auto-memory 的索引。
真正扮演“索引 + 主题文件”角色的,是后面的 MEMORY.md 系统。

1. Prompt 负责当前任务

Prompt 解决的是这一次你到底要 Claude 做什么。

比如:

  • 这次只修 bug,不要顺手重构
  • 这次只分析原因,先不要改代码
  • 这次只改前端,不动后端

这些都是单次任务边界,适合写在 prompt 里,不适合沉淀进 CLAUDE.md

2. 项目文档负责完整背景

README、设计文档、接口文档、架构说明,负责回答的是:

  • 这个项目是什么
  • 系统怎么设计
  • 模块如何划分
  • 业务流程怎么走

这些内容通常信息量大、细节多、更新频繁,它们的职责是“说明项目”,不是“约束 Claude 的默认行为”。

3. CLAUDE.md 负责稳定规则

CLAUDE.md 解决的是那些跨多次任务都成立、而且会持续影响 Claude 决策的东西。

比如:

  • 高风险文件和目录
  • 错误处理规范
  • 依赖策略
  • migration 边界
  • 哪些行为必须先确认

它不负责讲完整背景,只负责把真正影响行为的规则提炼出来。

4. Memory 负责自动沉淀

它更像 Claude 在长期协作里逐步学到的东西,是补充,不是替代。

你可以把它理解成“模型慢慢记住了你们项目里的某些偏好和事实”,但这类记忆不适合代替明确规则。因为对于关键边界来说,你明确写下来的东西,永远比它自己学到的更稳。

结合源码看,这个分工会更清楚:

  • CLAUDE.md 通过 instruction loader 进入系统 prompt
  • MEMORY.md 则是 auto memory 的入口文件
  • 相关 topic files 会在需要时被检索和召回,而不是把所有细节都塞进一个大文件

因此,更准确的理解是把它们视为“两套协作机制”,而不是“一份文件的两种叫法”。

5. Skill 负责重复流程

CLAUDE.md 管的是“长期规则”,Skill 更适合管“这类任务应该怎么做”。

比如:

  • 需求分析怎么展开
  • Code Review 按什么顺序做
  • 排查线上 bug 用什么流程
  • 新功能开发先看哪些文件、再做哪些验证

这类内容本质上是“做事模板”,更像流程,不像规则。

可以概括成一句话:

  • 当前任务Prompt
  • 稳定规则CLAUDE.md
  • 完整背景进项目文档
  • 自动沉淀交给 Memory
  • 重复流程沉淀成 Skill

一旦边界分清楚了,很多人最头疼的那个问题就会自动消失:

为什么我明明写了很多东西,但 Claude 还是不按我想的来?

因为你很可能把应该放在不同位置的信息,全塞进了 CLAUDE.md


五、三层分层架构

CLAUDE.md 不是一个单一文件,而是一个分层的规则系统。

三层分层架构

图:先按稳定性分层,再决定规则应该写到用户级、项目级还是公司级。

用户级~/.claude/CLAUDE.md
你电脑上所有项目都生效,写个人偏好。

项目级:仓库根目录的 CLAUDE.md
只在这个项目生效,写项目特有约定,提交进 Git。

公司级:企业统一管理的配置位置
整个组织生效,写合规要求和架构标准。大型企业才更常用,普通团队通常不需要。

判断一条规则放哪层,只用一个标准:

换个项目还成立吗?

成立放用户级。
比如:“我的变量命名用驼峰。” 换到任何项目都一样。

不成立放项目级。
比如:“这个项目用 Prisma,禁止用 Sequelize。” 换到 MongoDB 项目就不适用了。

这个区分看起来简单,但它直接决定后面的维护成本。


六、用户级:写你的默认行为偏好

用户级规则要少而精,不超过 50 行。这里写的是覆盖 Claude 默认值的个人偏好。

# 我的个人偏好

## 代码风格
- 缩进:2 个空格
- 变量命名:camelCase,类名 PascalCase
- 单行不超过 100 字符

## 我固定用的库(不要建议替代品)
- 日期处理:date-fns,不用 moment.js
- HTTP 请求:axios,不用 node-fetch
- 测试:Jest,不用 Vitest 或 Mocha

## 从不做的事
- 不要在我没要求时修改测试文件
- 不要建议我换版本控制工具
- 不要在随意讨论时提出架构大改动

## Git 提交格式
feat(模块名): 简短描述

- 改动说明 1
- 改动说明 2

注意措辞:写的是“我的偏好”,不是“你必须”。前者 Claude 当作信息接收,后者听起来像命令,反而可能在某些场景被跳过。

用户级不该写什么

  • 一次性的任务背景
  • 大段项目文档
  • 经常变动的技术现状
  • 只在某个仓库成立的规则

比如:“我现在在做一个电商系统。” 这不是偏好,是当前任务。应该放在 prompt 里。


七、项目级:记录这个项目特有的边界

项目级可以稍长,100 行左右。核心是三类内容:

1. 关键文件保护

## 禁止修改的文件
- src/config/env.ts — 改了会影响生产环境变量加载
- .github/workflows/* — CI/CD 流水线,改动需要 DevOps 审核
- 数据库 migration 文件一旦执行,不得修改,只能新增

2. 编码规范,必须具体到代码动作

## 错误处理
统一使用 AppError 类,禁止 throw new Error():
throw new AppError('用户不存在', 404, 'USER_NOT_FOUND')

## API 响应格式
所有响应必须符合:
{ "success": true, "data": {}, "timestamp": "ISO字符串" }
错误响应:
{ "success": false, "error": "ERROR_CODE", "message": "描述" }

3. 高风险路径标注

## 高风险区域(修改前必须告知我)
- src/modules/auth/* — 认证核心,任何改动都需要 review
- src/handlers/payment/* — 对接支付商,出错直接影响收入
- src/database/migrations/* — 不可逆操作,要有回滚方案

项目级真正决定效果的,不是“把整个项目介绍一遍”,而是:

把这个仓库里最容易做错、最不能做错的东西写出来。


八、一条好规则到底该怎么写

很多人不是不会列规则,而是写出来之后没有约束力。

比如:

  • 代码要整洁
  • 数据库迁移要小心
  • 不要随便改配置

这些话人类看得懂,但模型不一定知道“到底怎样做才算遵守”。

一条好规则,尽量包含这几个元素:

  • 触发场景
  • 期望动作
  • 禁止动作
  • 原因
  • 示例

一条好规则怎么写

图:好规则最少要把场景、动作、边界和原因交代清楚。

看一个例子就很清楚。

❌ 只有规则
- 使用 Prisma 生成迁移,不要写原生 SQL
✅ 规则 + 原因 + 行为边界
- 涉及 schema 变更时,优先走现有 migration 工作流,不要临时手写 SQL 直接改结构。
  原因:团队的审查、回滚和环境同步流程都围绕当前 migration 体系建立。
  如果必须做破坏性变更,先说明影响范围和回滚方案。

再比如:

❌ 太抽象
- 注意统一错误处理
✅ 可执行
- 所有业务异常统一使用 AppError,禁止直接 throw new Error()。
  原因:前端依赖统一错误码和 message 做提示与埋点归类。

关键就在这里:

CLAUDE.md 不是写原则,而是写可执行规则。


九、为什么有时有效,有时又像没生效

这也是很多人真正困惑的地方。

不是所有写进 CLAUDE.md 的内容,效果都一样。有些规则一写进去,Claude 的行为马上变化;有些规则写了之后,几乎没感觉。

通常不是因为它“没读”,而是因为规则本身写得不够能执行。

第一,规则写成了背景介绍

例如:

本项目采用分层架构,强调可维护性和扩展性。

这句话是背景,不是约束。Claude 即使看到了,也很难从里面推导出具体行动。

第二,规则太抽象

例如:

- 代码要整洁
- 注意性能
- 数据库修改要谨慎

这些话人类看得懂,但模型不知道“怎样才算遵守”。

第三,规则太多,信噪比下降

不是说长文一定不好,而是低价值内容一多,真正重要的规则就会被埋掉。

如果一份 CLAUDE.md 里面既有项目概述、又有接口说明、又有架构文档、又有零碎提醒,那 Claude 真正应该优先遵守的那些边界,反而不够突出。

第四,规则之间互相冲突

比如你在用户级里写了“我习惯四空格缩进”,项目级里又写“这个项目统一两空格”,但没有说明项目级覆盖团队标准。
这种情况下,Claude 不是一定做错,而是判断空间会变大。

第五,单次任务 prompt 和长期规则打架

如果你在 CLAUDE.md 里长期写“默认不要大改”,但当前 prompt 又说“请你重构这一块并统一风格”,那单次任务会临时改变优先级。

这不是 CLAUDE.md 失效,而是上下文优先级在变化。

真正决定它能不能稳定生效的,是三件事:

规则足够具体,边界足够清楚,信噪比足够高。只有这三件事同时成立,CLAUDE.md 才会真正改变行为。


十、它很重要,但不是万能控制器

把这一点想清楚,对 CLAUDE.md 的期待反而会更稳。

CLAUDE.md 很强,但它不是万能控制器。它做不到下面这些事:

  • 它不能替代清晰的任务描述
  • 它不能替代 README 和设计文档
  • 它不能替代你对复杂任务的即时判断
  • 它不能保证 Claude 在任何场景下 100% 机械执行

它真正擅长的是:

  • 让默认行为更接近你的项目习惯
  • 让高风险边界更早暴露
  • 让重复提醒沉淀成长期规则
  • 让每次 prompt 更聚焦当前任务

所以最好的理解方式不是:

“只要我把 CLAUDE.md 写好了,后面什么都不用管了。”

而是:

“我用 CLAUDE.md 把稳定规则立住,再用 prompt 管当前任务,用文档承载背景,用 Skill 沉淀流程。”

只有在这套分工里,CLAUDE.md 的作用才会既强,又稳定。


十一、连接第 03 篇:为什么它能解决“纠正回退”

第 03 篇讲过一个现象:你在对话里纠正了 Claude,它承认了,但过几轮又犯同样的错。这不是 Claude 不配合,而是对话历史会随时间衰减,纠正效果也会随之消退。

更稳定的纠正方式,就是写进 CLAUDE.md

写进 CLAUDE.md 的规则,每次对话开始时都会被系统自动注入,不受对话长度影响,也不会像临时纠正那样快速衰减。

判断标准很简单:

同一件事你纠正了两次以上,就应该写进 CLAUDE.md,不要再在对话里重复说。

# 这条规则在对话里说了三次,该进 CLAUDE.md 了:
- 日志统一用 logger.info/warn/error,禁止 console.log

十二、Claude 会主动学习,但它补充不了规则层

CLAUDE.md 不是单向的。你往里写规则,Claude 也会在长期协作中逐步积累知识。

每轮对话结束后,Claude Code 会在后台启动一个独立的子 Agent,分析对话里有没有值得保留的项目知识,自动写入 Memory 文件,下次会话时注入:

对话结束 → 后台子 Agent 分析 → 提取项目偏好和技术决策
→ 写入 ~/.claude/projects/[项目]/memory/ → 下次会话自动读取

你在某次对话里说了“我们禁止用 moment.js,改用 date-fns”,下次打开 Claude Code,它已经记得了。

几个要知道的细节:

它补充 CLAUDE.md,不取代它。
自动记忆是“Claude 学到的”,CLAUDE.md 是“你明确要求的”,关键约束还是应该写在 CLAUDE.md 里。

明确说出来的比隐含的更容易被记住。
在对话里直接说“我们统一用 date-fns”,提取率更高;只是悄悄在代码里换了库,Claude 可能记不到。

你可以检查它记了什么。
/memory 命令可以查看当前记忆内容,发现记错了直接改,它本质上还是普通文本文件。

实际效果是:Claude Code 越用越懂你的项目。头几天需要反复解释背景,用了几周后,很多背景已经自动沉淀,你的 prompt 可以越写越短。

Memory 的索引 + 主题文件结构

图:从源码看,Memory 更像索引入口 + topic files,而不是一个无限膨胀的大文件。

从源码看,Memory 本质上是一套“索引 + 主题文件”的结构

从实现上看,auto memory 不是把内容都堆在一个文件里。相关实现可以看 src/memdir/memdir.ts。在这部分实现里,入口常量就是:

export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000

这三行信息已经说明了很多问题:

第一,真正被当作 memory 入口文件的,是 MEMORY.md,不是 CLAUDE.md
第二,系统从设计上就不希望这个入口文件无限膨胀。
第三,memory 架构默认就不是“把所有内容堆在一个大文件里”。

同一个文件里,源码把保存流程直接写成了两步:

  1. 先把记忆写入独立主题文件
  2. 再在 MEMORY.md 里增加一个索引指针

源码注释原话基本就是这个意思:

  • Step 1:write the memory to its own file
  • Step 2:add a pointer to that file in MEMORY.md

而且它还专门强调:

MEMORY.md is an index, not a memory

从实现上看,Claude Code 的 auto memory 更像:

  • MEMORY.md:目录页 / 索引页
  • topic files:按主题拆开的详细内容

这也解释了一个很多人会问的问题:

如果记忆越积越多,MEMORY.md 不会越来越大吗?

答案是:源码层面已经考虑了这个问题。

truncateEntrypointContent() 会对 MEMORY.md 做双重限制:

  • 超过 200 行会截断
  • 超过 25KB 也会截断

截断后甚至还会追加警告,提醒把细节移到 topic files,只把一行短索引留在 MEMORY.md

换句话说,这套设计本身就在强制你保持:

  • 索引足够短
  • 细节分散到主题文件
  • 入口文件永远尽量装得进上下文

这和 CLAUDE.md 的定位,是什么关系

最容易混在一起的,恰恰是规则系统和记忆系统。

如果站在源码架构的角度看:

  • CLAUDE.md 更像 instruction layer
  • MEMORY.md 更像 memory index layer
  • topic files 更像 memory payload layer

这三层不是互相替代,而是互相配合。

所以把 CLAUDE.md 定义成“规则层”是成立的,而且和源码是对齐的。

放到 Claude Code 的完整架构里看,CLAUDE.md 负责规则,MEMORY.md 负责记忆索引,topic files 负责详细内容。

这样去理解,规则、索引和记忆详情各自负责什么,就不会再混成一团。

从源码看“自愈”和写入一致性

把这套机制类比成一种带“自愈”倾向的写入纪律,可以作为理解辅助,但不宜把类比直接当成源码结论。

从目前能看到的实现和解析文档来看,至少可以确定三件事:

  • memory 保存采用“先写主题文件,再更新 MEMORY.md 指针”的两步方式
  • 这种顺序天然更有利于一致性,因为索引最终指向的是已经成功落盘的内容
  • 它的思路更接近“先落数据,再更新索引”,和很多数据库 / 存储系统的一致性设计取向相似

更稳妥的理解是,把它当作一种 可以类比理解 的一致性思路,而不是直接把它等同于“源码明确实现了 WAL 逆向”。

因为源码里我能确认的是:

  • 两步保存存在
  • MEMORY.md 是索引存在
  • 入口大小控制存在
  • 按需检索 topic files 存在

这些都是可以直接从源码和解析文档里站得住的。


十三、两个最常见的陷阱

陷阱一:写得太多,关键规则被淹没

CLAUDE.md 写得太长时,Claude 往往只会抓住其中最显眼、最强约束的那部分,其他内容会逐渐退化成背景噪音。规则越多,真正稳定生效的比例通常越低。

解决方法:

  • 定期删掉已经不再是问题的规则
  • 删掉太细节、没有行为约束力的规则
  • 删掉重复表达

CLAUDE.md 应该是个活跃的 hotlist,不是越来越臃肿的文档。

陷阱二:规则放错层级

用户级放了项目特有规则,Claude 在其他项目里也按这个来。
项目级放了所有项目通用规则,十几个项目各自维护一份重复内容,改一条要改十几个地方。

解决方法还是那一句:

换个项目还成立吗?

成立放用户级,不成立放项目级,一次定好就别再改。


十四、维护节奏

CLAUDE.md 写好之后不是扔着不管,需要定期维护。

第一个月:初始化

/init 生成草稿,花半小时补充:

  • 关键文件保护
  • 错误处理规范
  • API 格式约定
  • 高风险路径说明

这是最重要的一次,做好了后面会省很多事。

每两周:维护

回顾最近 Claude 犯过什么错。

  • 同一个错出现两次以上,加进 CLAUDE.md
  • 已经不构成问题的规则,删掉
  • 写得太空的规则,改具体一点

每季度:清理

把整个文件读一遍:

  • 删冗余
  • 合并重复
  • 简化过细规则

目标是让文件保持高信噪比,而不是越写越长。


十五、检查清单

提交项目级 CLAUDE.md 前过一遍:

  • 规则是纠偏式的,不是文档式的
  • 每条规则能对应到 Claude 的一个具体行为变化
  • 关键文件有明确的保护声明
  • 高风险路径有标注和警告
  • 重要规则附上了“为什么”
  • 用户级和项目级没有混放
  • 文件总长度不超过 200 行
  • 对话里纠正过两次以上的规则已经写进来了

本篇实践任务

任务一: 打开你现有的 CLAUDE.md,把里面每条规则过一遍:它是纠偏式,还是文档式?把文档式的删掉或者改成纠偏式。

任务二: 回想最近一周,你在对话里纠正过 Claude 几次同一个问题?把这些问题整理成具体规则,写进 CLAUDE.md,下次对话观察效果。

任务三: 运行 /memory,看看 Claude 已经自动记住了什么。和你的 CLAUDE.md 对比,有没有重复的内容?有没有记错的内容需要修正?


下篇预告

第 05 篇:Skill 提炼——把重复任务沉淀成可复用模板

CLAUDE.md 管的是全局规则,Skill 管的是任务模板。当同一类任务反复出现,把“怎么做这类任务”浓缩成一个 Skill,下次直接触发。下一篇会讲什么时候沉淀 Skill、怎么写一个真正有效的 Skill,以及 Skill 和自定义命令的边界在哪。


AI Coding 系列持续更新。CLAUDE.md 是规则层,不是项目文档。写法不同,效果天壤之别。

实现记忆开关

本次功能

记忆开关

支持:

  • 每个会话单独开启 / 关闭记忆注入
  • 关闭后仍保留记忆数据,但不会参与回答
  • 方便对比“有记忆”和“无记忆”的回复差异

1)改 web/src/utils/session.js

createSession 里新增字段

  return {
    id: crypto.randomUUID(),
    title,
    mode,
    customPrompt: persona.systemPrompt,
    temperature: 0.7,
    topP: 1,
    maxTokens: 1200,
    memoryEnabled: true,
    pinned: false,

loadSessions 里的 normalize 增加默认值

      return {
        mode,
        customPrompt:
          item.customPrompt ||
          item.messages?.find(m => m.role === 'system')?.content ||
          persona.systemPrompt,
        temperature: 0.7,
        topP: 1,
        maxTokens: 1200,
        memoryEnabled: true,
        pinned: false,
        ...item,
      }

2)改 server/app.py

ChatRequest 新增字段

class ChatRequest(BaseModel):
    messages: List[Message]
    session_id: Optional[str] = None
    temperature: Optional[float] = 0.7
    top_p: Optional[float] = 1
    max_tokens: Optional[int] = 1200
    memory_enabled: Optional[bool] = True

build_final_messages 改成支持记忆开关

def build_final_messages(messages: List[dict], session_id: str = "", memory_enabled: bool = True):
    session_memories = get_session_memories(session_id or "") if memory_enabled else []
    memory_prompt = build_memory_prompt(session_memories) if memory_enabled else ""

    final_messages = []
    for item in messages:
        if item["role"] == "system":
            final_messages.append({
                "role": "system",
                "content": f'{item["content"]}\n\n{memory_prompt}'.strip()
            })
        else:
            final_messages.append(item)

    return final_messages, session_memories

/api/chat 里调用改一下

    final_messages, session_memories = build_final_messages(
        messages,
        req.session_id or "",
        req.memory_enabled if req.memory_enabled is not None else True,
    )

/api/chat/stream 里调用也改一下

    final_messages, session_memories = build_final_messages(
        messages,
        req.session_id or "",
        req.memory_enabled if req.memory_enabled is not None else True,
    )

3)改 web/src/App.vue

新增计算属性

const currentMemoryEnabled = computed(() => {
  return currentSession.value?.memoryEnabled ?? true
})

新增状态

const memoryEnabledDraft = ref(true)

增加监听

watch(
  currentMemoryEnabled,
  newVal => {
    memoryEnabledDraft.value = !!newVal
  },
  { immediate: true }
)

新增保存方法

const handleSaveMemorySetting = () => {
  if (!currentSession.value) return

  sessions.value = sortSessions(
    sessions.value.map(item =>
      item.id === currentSessionId.value
        ? {
            ...item,
            memoryEnabled: !!memoryEnabledDraft.value,
            updatedAt: Date.now(),
          }
        : item
    )
  )
}

流式请求 body 增加字段

    body: JSON.stringify({
      messages,
      session_id: currentSession.value.id,
      temperature: currentSession.value.temperature,
      top_p: currentSession.value.topP,
      max_tokens: currentSession.value.maxTokens,
      memory_enabled: currentSession.value.memoryEnabled,
    }),

4)改模板

在参数面板里追加记忆开关块

<div class="param-item">
  <label class="param-label">memory</label>

  <div class="memory-switch-row">
    <label class="memory-switch-label">
      <input v-model="memoryEnabledDraft" type="checkbox" />
      <span>{{ memoryEnabledDraft ? '开启记忆注入' : '关闭记忆注入' }}</span>
    </label>

    <button class="prompt-btn small" @click="handleSaveMemorySetting">保存记忆设置</button>
  </div>

  <div class="param-tip">关闭后会保留记忆数据,但不会注入到对话上下文</div>
</div>

5)补充样式

.memory-switch-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.memory-switch-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: #111827;
}

.prompt-btn.small {
  padding: 6px 10px;
  font-size: 12px;
}

6)怎么验证

image.png

image.png

nice !

本次 提交修改 代码

github.com/fhj414/ai-c…

完整代码请看仓库,仓库地址:github.com/huanhunmao/… star 🌟🌟🌟 谢谢~

AI 全流程解析(LLM / Token / Context / RAG / Prompt / Tool / Skill / Agent)

前言

AI圈子里每天都在冒新名词:LLM、Token、Context、Prompt……这些词你可能都听说过。但是,你真的能准确说出其中每一个概念的确切含义吗?

这篇文章不整那些虚头巴脑的商业概念,我们从最底层的工程视角出发,一个一个把这些概念拆开、揉碎讲清楚。相信读完这篇文章,你对AI的理解绝对会上升一个台阶。

image.png


一、LLM(大语言模型)

1.1 什么是LLM?

LLM全称是Large Language Model,翻译成中文就是大语言模型,简称大模型

基本上现在所有的大模型都是基于Transformer这套架构训练出来的。这个架构最早由Google团队在2017年提出,对应的论文名是《Attention is All You Need》。

极具戏剧性的是,虽然Google发明了火种,但真正把它点燃并且引爆全世界的却是OpenAI。

1.2 大模型的发展历程

  • 2020年:GPT-3发布,已经具备初步实用价值
  • 2022年底:GPT-3.5横空出世,真正让大模型进入大众可用阶段
  • 2023年3月:GPT-4发布,把AI的能力天花板拉到了新高度
  • 至今:Claude等优秀后起之秀在各领域与OpenAI同台竞技

GPT系列是今天AI浪潮的绝对鼻祖,时至今日,GPT家族依然非常强大,GPT-4仍是业界标杆之一。

1.3 大模型的工作原理

大模型到底是怎么工作的?答案非常简单朴素:它本质上就是一个文字接龙游戏

虽然本质是"预测下一个Token",但通过大规模训练,这种能力涌现出了推理、总结、代码生成等复杂行为。

具体例子:

假设你向大模型提问:"这个产品怎么样?"

  1. 模型接收这句话后,经过内部运算,预测下一个概率最高的词:"非常"
  2. 模型把"非常"抓回来,追加到输入后面
  3. 继续预测下一个字:"好"
  4. 再把"好"塞回去,继续预测:"用"
  5. 最后输出结束标识符

完整回答:"非常好用"

这就是为什么大模型要一个词一个词地输出答案——因为它就是这么运作的。

一句话总结:大模型 = 一个极其复杂的"概率文字接龙机器"


二、Token(词元)

2.1 文字与数字的桥梁

大模型本质上是一个庞大的数学函数,里面跑的全是矩阵运算。它接收的是数字,输出的也是数字,压根儿就不认识人类写的文字。

在人类和大模型之间必须有一个中间人来做翻译,这个中间人就叫做Tokenizer(分词器),负责编码和解码两件事情:

  • 编码:把文字变成数字
  • 解码:把数字还原成文字

2.2 Token的生成过程

以用户提问"这个产品怎么样?"为例,这句话通常会被切分成若干个Token(具体数量取决于模型的分词策略,不同模型可能略有差异)。

第一步:切分

把用户的问题拆成一个一个最小的片段,这些片段就叫做Token

第二步:映射

把每个Token对应到一个数字上去,这个数字就叫做Token ID

2.3 Token vs 词:不是一对一关系

你可能会想:Token就是词,对吧?

不一定! Token和词并没有明确的一对一关系。

中文例子:

  • "工作坊" → 拆成"工作"+"坊"(2个Token)
  • "程序员" → 拆成"程序"+"员"(2个Token)

英文例子:

  • "hello" → 1个Token
  • "going" → 1个Token
  • "helpful" → 拆成"help"+"ful"(2个Token)

特殊字符:

某些情况下,一个字符会被切分成多个Token。比如"✓"(对勾)需要3个Token来表示。

2.4 Token的估算标准

平均来讲:

  • 1个Token ≈ 0.75个英文单词
  • 1个Token ≈ 1.5~2个汉字

举例:

  • 40万个Token ≈ 60~80万个汉字
  • 40万个Token ≈ 30万个英文单词

总结:Token是模型自己学会的一套文本切分规则,切出来的每一块就是它一次能够处理的最小单位。

一句话总结:Token是模型理解世界的"最小语言单位"


三、Context(上下文)

3.1 大模型的"记忆"之谜

我们平时和大模型聊天,它好像能记住之前说的话。比如你开头告诉他"我的名字是小明",他给你回复以后,你再问他"我叫什么名字",他还是能够回答得出来。

但问题是,大模型本质上只是一个数学函数,它并不像人一样真的有记忆。它是怎么记住之前的聊天内容的呢?

答案:每次给大模型发送消息时,并不只会发我们的问题,背后的程序会自动把之前的整段对话历史找出来,一起发过去。

3.2 什么是Context?

Context(上下文)代表大模型每次处理任务时所接触到的信息总和。

Context包含的内容:

  1. 用户问题
  2. 对话历史
  3. 大模型正在输出的每个Token
  4. 工具列表
  5. System Prompt(系统提示词)
  6. 其他信息

可以把Context看成是大模型的一个临时记忆体。

3.3 Context Window(上下文窗口)

Context Window代表Context能够容纳的最大Token数量。

主流模型的Context Window:

模型 Context Window
GPT-4 128K(约105万Token)
Claude 3.1 Pro 100万Token
Claude Opus 4 100万Token

100万个Token ≈ 150万个汉字,整个《哈利波特》全集的内容都能装下。

技术细节:为什么Context会影响推理能力?因为大模型进行复杂推理(如链式思考)需要足够长的上下文来"记住"推理过程。没有足够的Context,模型无法进行多步骤的逻辑推演。

一句话总结:Context不是记忆,是一次性打包输入


四、RAG(检索增强生成)

4.1 问题场景

假设你有一个上千页的公司产品手册,你希望大模型根据这个手册来回答用户的各种疑问,要怎么实现?

4.2 不好的方案

把手册的全部内容跟着用户问题一起扔给大模型?

问题

  • 产品手册太长
  • 即使模型的Context Window不被撑爆,成本也无法控制

4.3 RAG解决方案

RAG(Retrieval-Augmented Generation,检索增强生成)可以从产品手册中抽取与用户问题最为匹配的几个片段,然后只把这几个片段发给大模型。

优势

  • 不受Context Window大小限制
  • 成本大大降低
  • 大模型接收的不是一整本书,可能只是几段话

RAG vs Fine-tuning:RAG是检索时动态注入知识,适合知识频繁更新的场景;微调是将知识嵌入模型参数,适合特定任务的优化。两者可以结合使用。

一句话总结:RAG = 给大模型外挂一个可检索的知识库


五、Prompt(提示词)

5.1 什么是Prompt?

Prompt(提示词)是大模型接收的具体问题或指令。

比如你向大模型提需求:"帮我写一首诗。"这句话就是Prompt。

不要把Prompt想成特别复杂高端的东西,它只不过就是给大模型的一个问题或者是指令而已。

5.2 为什么Prompt很重要?

如果你只是简单地说"帮我写一首诗",大模型可能会:

  • 写古诗
  • 写现代诗
  • 写打油诗

原因:Prompt太模糊,它不知道你具体想要什么。

5.3 如何写好Prompt?

一个好的Prompt应该是:清晰的、具体的、明确的

好的例子

"请帮我写一首五言绝句,主题是秋天的落叶,风格要悲凉一点。"

这样一来,大模型就清楚多了,生成的内容也更符合你的预期。

5.4 Prompt Engineering(提示词工程)

Prompt Engineering本质上不是"黑科技",而是"把话说清楚"——这也是为什么它门槛并不高。

现状

  • 门槛较低,本质上就是把话说清楚
  • 大模型能力越来越强,即使提示词含糊不清,大模型也能大致猜出你的意图
  • 现在还在提它的人寥寥无几

一句话总结:Prompt = 你和大模型沟通的唯一接口


六、User Prompt vs System Prompt

6.1 两种不同的Prompt

有些时候我们不仅要告诉大模型它要处理的具体任务,还要告诉它人设和做事规则。

User Prompt(用户提示词)

  • 定义:说明具体任务的Prompt
  • 来源:用户自己在对话框输入
  • 示例:"3加5等于几?"

System Prompt(系统提示词)

  • 定义:说明人设和做事规则的Prompt
  • 来源:开发者在后台配置
  • 示例:"你是一个耐心的数学老师,当学生问你数学问题的时候,不要直接给出答案,而是要一步一步引导学生思考,帮助他们理解解题思路。"

6.2 具体例子

场景:做一个数学辅导机器人

System Prompt(后台设置,用户看不到):

"你是一个耐心的数学老师,当学生问你数学问题的时候,不要直接给出答案,而是要一步一步引导学生思考,帮助他们理解解题思路。"

User Prompt(学生输入):

"3加5等于几?"

大模型的回答

"我们可以这样想,你手里有三个苹果,然后又拿了5个,现在一共有多少个呢?你可以数一数看。"

对比:如果没有System Prompt,大模型可能直接说"8"了。


七、Tool(工具)

image.png

7.1 大模型的弱点

大模型有一个明显的弱点:无法感知外界环境

没有Tool的大模型,本质上是"闭着眼睛说话"。

例子:

假设你问大模型:"今天上海的天气怎么样?"

它可能会说:

"抱歉,我无法获取实时天气信息。我的知识库截止到某年某月,无法提供当前的天气数据。"

原因:大模型只是个文字接龙游戏,它的能力是根据训练数据来预测下一个词,但它真的没有办法去查天气预报网站拿到实时的天气数据。

7.2 什么是Tool?

Tool(工具)本质上就是一个函数,你给它输入,它就给你输出。

天气查询工具例子:

  • 输入:城市、日期(两个参数)
  • 内部操作:调用气象局的接口
  • 输出:天气信息

有了工具,大模型就可以回答天气相关的问题了。

7.3 工作流程

完整流程涉及的角色:

  1. 用户:提出问题
  2. 大模型:理解问题,决定是否需要调用工具
  3. 工具:执行具体任务,返回结果
  4. 大模型:根据工具返回的结果,生成最终回答

具体步骤:

  1. 用户问:"今天上海天气怎么样?"
  2. 大模型识别到需要天气信息
  3. 大模型调用天气查询工具,传入参数:城市="上海",日期="今天"
  4. 工具调用气象局API,返回天气数据
  5. 大模型根据天气数据生成自然语言回答
  6. 用户收到:"今天上海晴,气温25°C,适合出行。"

7.4 常见工具类型

工具类型 功能 示例
搜索工具 实时搜索互联网信息 Google搜索、Bing搜索
计算工具 执行数学计算 Python代码执行器
数据库工具 查询数据库 SQL查询工具
API工具 调用外部服务 天气API、股票API
文件工具 读写文件 文档处理工具

一句话总结:Tool = 让大模型睁开眼睛看世界的能力


八、Skill(技能)

我帮你写一版“风格统一的”👇(可以直接用)


什么是Skill?

Skill(技能)是针对特定任务的预配置能力包。它把大模型、Prompt、工具、记忆等组件打包在一起,形成一个可以直接使用的功能模块。

如果说:

  • Tool 是一个个“工具函数”
  • 那 Skill 就是把多个 Tool + Prompt + 执行流程 组合在一起,形成一个可复用的能力模块

Skill的组成

组件 说明 示例
大模型配置 选择哪个模型 GPT-4、Claude、Gemini
System Prompt 人设和任务规则 "你是一个Python代码专家"
工具列表 可调用的工具 代码执行器、Git操作
记忆配置 是否需要记忆 短期记忆、长期记忆
输出格式 结果的格式要求 JSON、Markdown、代码块

Skill vs Agent vs SubAgent

对比维度 Skill Agent SubAgent
定位 功能模块 任务执行者 辅助执行者
配置 预配置 动态配置 由Agent分配
复用性 高(可跨项目) 中(项目内) 低(任务内)
自主性 低(被动调用) 高(自主规划) 低(被动执行)

Skill的生态系统

开源Skill库

  • GitHub上的Skill仓库
  • 社区贡献的Skill包
  • 可直接下载使用

商业Skill市场

  • 官方Skill商店
  • 第三方Skill平台
  • 付费/免费Skill

自定义Skill

  • 根据业务需求定制
  • 企业内部Skill库
  • 持续迭代优化

九、Agent(智能体)

9.1 从大模型到Agent

前面我们学习了Tool,让大模型能够调用外部函数来获取信息。但这里有个问题:谁来决定什么时候调用工具?调用哪个工具?工具返回的结果怎么处理?

这就是Agent(智能体)登场的时候了。

Agent是大模型的进阶形态,它不仅能理解用户需求,还能自主规划和执行任务。

Agent不是更聪明,是更会做事。

9.2 Agent的核心能力

一个完整的Agent通常具备以下能力:

1. 感知能力

理解用户的意图和当前环境信息。

2. 规划能力

把复杂任务拆解成多个步骤,制定执行计划。

3. 执行能力

调用工具、访问外部API、操作文件等。

4. 反思能力

评估执行结果,调整策略,重新规划。

9.3 Agent vs 大模型的区别

对比维度 大模型 Agent
核心能力 文本生成 任务执行
主动性 被动响应 主动规划
工具使用 需要人工指定 自主决定调用
任务处理 单轮对话 多步骤任务流
记忆能力 有限(Context限制) 可扩展(外部存储)
错误处理 可重试、调整策略

9.4 Agent工作流程示例

场景:用户让Agent帮忙订一张去北京的机票

步骤1:理解意图

Agent分析用户需求:"订去北京的机票"

步骤2:规划任务

Agent把任务拆解:

  • 确定出发城市
  • 确定出发日期
  • 查询航班信息
  • 选择合适航班
  • 完成预订

步骤3:执行与交互

  1. Agent问:"请问您从哪个城市出发?"
  2. 用户答:"上海"
  3. Agent问:"请问您希望什么日期出发?"
  4. 用户答:"明天"
  5. Agent调用航班查询工具
  6. Agent展示查询结果
  7. Agent问:"您选择哪个航班?"
  8. 用户选择后,Agent调用预订工具

步骤4:反馈与确认

Agent返回:"已为您预订成功,订单号是..."

8.5 SubAgent(子智能体)

什么是SubAgent?

SubAgent是Agent的子智能体,用于处理Agent任务流程中的子任务。

Agent vs SubAgent的区别

对比维度 Agent SubAgent
定位 主控智能体 辅助智能体
任务范围 完整任务 子任务
调用关系 被用户调用 被Agent调用
生命周期 长期存在 任务完成后销毁
决策权 低(由Agent分配)

SubAgent使用场景

例子:用户让Agent写一个技术文档

  1. Agent接收到任务:"写一份API文档"
  2. Agent规划
    • 调用SubAgent1:分析代码,提取API接口信息
    • 调用SubAgent2:生成文档模板
    • 调用SubAgent3:填充具体内容
  3. SubAgent1完成代码分析,返回接口列表
  4. SubAgent2生成文档结构
  5. SubAgent3根据接口信息填充文档内容
  6. Agent整合所有结果,返回最终文档

一句话总结:Agent = 能自己决定怎么做事的大模型

8.6 多Agent协同模式

为什么需要多Agent协同?

单个Agent虽然强大,但面对复杂任务时,往往需要"术业有专攻"。通过多个Agent协同工作,可以实现:

  • 专业分工:每个Agent专注自己的领域
  • 质量提升:通过互相检查、辩论,减少错误
  • 效率优化:并行处理多个子任务
  • 能力互补:不同Agent拥有不同的工具和知识

动态规划示例

  • 代码审查Agent:挂载代码分析工具,使用Claude模型(擅长代码理解)
  • 数据分析Agent:挂载数据处理工具,使用GPT-4模型(擅长数学推理)
  • 文档写作Agent:挂载文档模板工具,使用Gemini模型(擅长长文本生成)

三种主流协同模式

image.png

一、上下级协同(Hierarchical)——类比公司组织架构

结构image.png

工作方式

  • 中控Agent负责拆解任务、分配工作、整合结果
  • 子Agent负责具体执行
  • 可以多层嵌套,形成树状结构

适用场景

  • 大型项目管理
  • 复杂系统的开发
  • 需要严格流程控制的任务

实际案例 - 飞猪行程规划

  • 中控Agent:接收用户"规划一次北京到上海的旅行"需求
  • SubAgent1:查询北京到上海的航班信息
  • SubAgent2:搜索上海的酒店和景点
  • SubAgent3:生成详细的行程安排
  • 中控Agent:整合所有信息,输出完整行程单

实际案例 - 机票预订

  • 中控Agent:接收"预订明天北京到上海的机票"需求
  • SubAgent1:查询航班信息(时间、价格、航空公司)
  • SubAgent2:对比不同航班的性价比
  • SubAgent3:执行预订操作
  • 中控Agent:确认预订结果,返回订单号
二、师生式协同(Master-Disciple)——本质是"带思路"

结构

image.png工作方式

  • 专家Agent提供策略、方法论、评价标准
  • 新手Agent按照专家的指导执行具体任务
  • 专家可以实时反馈和调整

关键要素

  • 策划思路:如何拆解问题、制定策略
  • 信息收集方法:从哪里获取信息、如何筛选有效信息
  • 表达格式规范:输出应该是什么格式、包含哪些要素
  • 评价标准:如何判断结果的好坏、如何改进

适用场景

  • 需要传承专业知识的任务
  • 质量要求高的内容创作
  • 需要标准化流程的工作

实际案例 - 单轮对话优化

  • 用户输入:"帮我写一个产品介绍"
  • 新手Agent:生成初步版本(可能不够专业)
  • 专家Agent:提供反馈:"需要突出产品的核心优势,增加数据支撑,使用更专业的术语"
  • 新手Agent:根据反馈优化输出
  • 专家Agent:继续指导:"结构可以调整为:问题背景 → 产品解决方案 → 核心优势 → 客户案例"
  • 新手Agent:按照新结构重新生成
  • 最终输出:高质量的产品介绍

本质区别:上下级协同是主智能体严格拆解任务并分配,师生式协同则是通过讨论和反馈优化输出,更具互动性。

三、竞争式协同(Competitive / Debate)——让模型"互相杠"

结构image.png工作方式

  • 多个Agent独立生成不同方案
  • 裁判Agent对比分析各方案优劣
  • 选择最优或融合多个方案

本质:多解 → 对比 → 选择最优

适用场景

  • 开放性问题(没有标准答案)
  • 需要高质量输出的任务
  • 容易"幻觉"的任务(需要互相验证)
  • 创意类工作(需要多个视角)

实际案例 - 营销文案创作

  • 任务:"为一款智能手表写营销文案"
  • Agent1:强调性价比(价格优势、功能对比)
  • Agent2:强调品质(材质、工艺、品牌背书)
  • Agent3:强调创新(独特功能、技术突破)
  • 裁判Agent:综合三个角度,生成最优文案
    • 开头用创新点吸引注意力
    • 中间用品质数据建立信任
    • 结尾用性价比促成转化

为什么竞争式协同有效?

  • 避免单一视角:不同Agent从不同角度思考,避免思维局限
  • 互相验证:多个方案可以互相检查,减少幻觉和错误
  • 激发创意:竞争机制激发Agent的创造力,产生更好的想法
  • 质量提升:通过对比筛选,最终输出质量更高

应用场景总结

多智能体协作适用于复杂任务(如工程开发、项目上线),通过分工减轻单一智能体负担。

应用场景 推荐模式 理由
工程开发 上下级协同 需要严格的任务拆解和流程控制
项目上线 上下级协同 涉及多个环节,需要统一调度
代码审查 师生式协同 需要专家指导,逐步优化代码质量
内容创作 师生式协同 需要反复打磨,提升内容质量
方案设计 竞争式协同 需要多角度思考,选择最优方案
创意生成 竞争式协同 需要激发创意,避免思维固化
数据分析 混合模式 用上下级协同拆解任务,用竞争式协同验证结果

如何选择协同模式?

任务特点 推荐模式
复杂、需要严格流程 上下级协同
需要专业知识传承 师生式协同
开放性、创意类 竞争式协同
需要多角度验证 竞争式协同
大型项目管理 上下级协同
需要迭代优化 师生式协同

一句话总结:多Agent协同 = 让多个专业AI各司其职,通过分工、合作、竞争,共同完成复杂任务


十、思考题

Q1:Token和字符有什么区别?为什么大模型不直接处理字符?

  • Token是大模型处理文本的最小单位,Token和字符不是一对一关系
  • 一个Token可能对应一个词、多个字符,也可能一个字符对应多个Token
  • 平均1个Token ≈ 1.5~2个汉字,或0.75个英文单词
  • 不直接处理字符的原因:字符粒度太细,模型需要学习更多模式;Token粒度更符合语言单元,训练效率更高

Q2:Context Window越大越好吗?

  • Context Window是大模型一次能处理的最大Token数量
  • 它决定了模型能"记住"多少信息
  • 影响模型的成本和性能
  • 不同模型的Context Window大小不同(GPT-4:128K,Claude 3.1 Pro:100万)
  • 不是越大越好:更大的Context意味着更高的计算成本,需要根据实际需求选择合适的模型

Q3:RAG和Fine-tuning有什么区别?什么时候用哪个?

  • RAG:检索时动态注入知识,适合知识频繁更新的场景
  • Fine-tuning:将知识嵌入模型参数,适合特定任务的优化
  • RAG优势:知识可实时更新,成本低,不受Context Window限制
  • Fine-tuning优势:模型对特定领域更熟悉,输出更稳定
  • 两者可以结合使用:用Fine-tuning学习领域风格,用RAG获取最新知识

Q4:Agent和普通的大模型有什么本质区别?

  • Agent是具备自主规划和执行能力的智能体
  • 区别:
    • 大模型:被动响应,只能生成文本
    • Agent:主动规划,可以执行多步骤任务
  • Agent的核心能力:感知、规划、执行、反思
  • Agent可以自主决定何时调用工具、调用哪个工具
  • Agent不是更聪明,是更会做事

Q5:Agent未来的发展方向是什么?

技术方向

  • 多模态能力:处理文本、图像、音频、视频
  • 更强的规划能力:处理更复杂的任务链
  • 自主学习:根据反馈优化自身行为
  • 协作能力:多个Agent协同工作

应用方向

  • 垂直领域Agent:医疗、法律、金融等
  • 个人化Agent:深度了解用户习惯和偏好
  • 企业级Agent:处理复杂的业务流程
  • 物理世界Agent:机器人、自动驾驶等

挑战

  • 安全性:防止Agent执行危险操作
  • 可控性:确保Agent行为符合预期
  • 成本:降低Agent运行成本
  • 普适性:让更多普通人能使用Agent

十一、总结

核心概念回顾

概念 英文 定义
大语言模型 LLM 基于Transformer架构训练的大规模语言模型
词元 Token 大模型处理文本的最小单位
上下文 Context 大模型每次处理任务时接收的信息总和
上下文窗口 Context Window Context能容纳的最大Token数量
提示词 Prompt 大模型接收的具体问题或指令
检索增强生成 RAG 从大量文档中检索相关片段发给大模型的技术
工具 Tool 让大模型能够执行具体任务的函数
智能体 Agent 具备自主规划和执行能力的AI系统
子智能体 SubAgent 被Agent调用的辅助智能体
技能 Skill 针对特定任务的预配置能力包
模型上下文协议 MCP 连接大模型和外部数据源的标准化协议

image.png


十二、结语

AI技术日新月异,但核心概念始终如一。从最底层的LLM、Token,到中层的Context、Prompt、RAG,再到上层的Tool、Agent、Skill、MCP,这些概念构成了现代AI应用的技术栈。

希望这篇文章能帮助你建立扎实的AI知识体系。如果觉得有用,欢迎分享给更多需要的朋友!

:本文内容基于当前主流AI技术整理,随着技术发展,部分概念可能会有更新。建议持续关注最新动态。

Cursor + Claude Code 组合使用心得:我为什么不只用一个 AI 编程工具

Hi~大家好呀,我是清汤饺子。

之前写了不少 Cursor 和 Claude Code 的单独教程,但最近被问得最多的一个问题其实是: "你平时到底用哪个?两个都装了吗?"

我的答案是:都用。

不是因为我钱多烧得慌,而是这两工具真的互补。用了一段时间组合拳之后,我的开发效率又上了一个台阶。今天就来聊聊我的真实使用心得。


一、我踩过的坑:只用其中一个的局限

1. 只用 Cursor?热情过头容易翻车

刚开始我是 Cursor 重度用户,毕竟 IDE 里直接写代码的感觉很顺。但用久了发现一个问题:Cursor 太"积极"了

有时候我只是想改个小样式,它能给你整出来一套组件抽象。我只是想加个简单的加载状态,它直接给你上了一整套状态管理方案。

不是说不好,而是有时候我真的就只想——快点搞定,别整那么多花活。

2. 只用 Claude Code?终端操作有局限

后来切到 Claude Code,发现上下文理解确实强,代码质量也更高。但问题是:它毕竟是 命令行工具 ,对 GUI 项目有点不太友好

比如我想在浏览器里调试一个 CSS 动画,想在某个特定的光标位置试试某个交互——这种事在终端里做起来就没有那么直接。

3. 结论:没有银弹

所以我的结论是:两个工具各有各的擅长领域,硬要二选一反而是给自己找麻烦。组合使用才是最优解。


二、场景分工:什么时候用哪个

经过几个月的磨合,我是这么分工的:

1. Cursor 更适合这些场景

1. 快速原型搭建

当我要验证一个新想法或者快速搭一个 demo,Cursor 的多文件编辑能力很强,能一次性给你生成一整套页面。这种场景 Claude Code 也可以,但 Cursor 更"短平快"。

2. 批量文件修改

比如我要把项目里 20 个组件的样式从 less 迁移到 styled-components,或者批量替换某个 API 调用——这种事 Cursor 的批量编辑效率很高。

3. GUI 调试

在浏览器里点点改改,看看效果,这种事 Cursor 集成得更好。Claude Code 的话你得靠终端里的预览,体验稍差。

2. Claude Code 更适合这些场景

1. 代码审查

把一堆代码丢给 Claude Code,让它帮我 review 一下质量、看看有没有潜在的 Bug——这种事 Claude Code 做得很漂亮。上下文理解能力强,能发现一些我自己可能忽略的问题。

2. 复杂重构

当我要对一个大文件或者多个模块做重构的时候,我会用 Claude Code 出方案,然后让它一步步来。Cursor 虽然也能做,但复杂场景下 Claude Code 的规划能力更强。

3. 长对话需求

比如我要和 AI 讨论一个架构设计,或者让它帮我分析一段老代码的逻辑——这种需要多轮对话的场景,Claude Code 的体验明显更好。


三、工作流串联:1+1>2 的组合拳

光说场景可能还是有点虚,来说说我每天是怎么组合用这两个工具的。

1. 新功能开发流

  1. 用 Cursor 快速搭架子:新功能的页面和基础组件,用 Cursor 快速生成第一版
  2. 用 Claude Code 审查优化:把代码丢给 Claude Code,让它帮忙看看有没有改进空间
  3. 回到 Cursor 执行:根据 Claude Code 的建议,在 Cursor 里做精细调整

2. Bug 修复流

  1. 用 Cursor 定位问题:在代码里直接搜索定位,Cursor 的跳转和搜索功能比较好用
  2. 用 Claude Code 分析根因:把相关代码丢给 Claude Code,让它帮忙分析可能的原因
  3. 回到 Cursor 修复:确认方案后在 Cursor 里执行修复

3. 代码重构流

  1. 用 Claude Code 出方案:让它先分析现有代码,设计重构方案
  2. 用 Cursor 执行:根据方案在 Cursor 里一步步改,Cursor 的修改精度更高

4. 我的每日模板

早上开工:
→ 用 Claude Code 过一遍昨天的代码,快速 review
→ 用 Cursor 开始今天的功能开发

下午:
→ 遇到复杂问题切 Claude Code 对话
→ 批量修改切 Cursor 执行

收工前:
→ Claude Code 总结今天改动,生成 commit message

四、实战心得:三个月磨合出来的经验

1. Rules 和提示词怎么差异化配置

Cursor 和 Claude Code 我配的 Rules 不太一样:

Cursor侧重于项目的技术规范——用什么组件库、用什么命名方式、样式写在哪个文件里。这种偏"执行层"的东西 Cursor 更容易遵守。

Claude Code侧重于架构和设计原则——为什么要这样设计、有什么权衡、哪种方案更合理。这种偏"思考层"的东西 Claude Code 更擅长。

2. 两个工具的上下文如何互补

我有个小技巧:用 Claude Code 建立项目记忆

每次项目有重大架构调整或者加了新模块,我会用 Claude Code 做一个详细的分析和总结,然后把这个结论记在项目文档里。下次 Cursor 接手这个项目的时候,就能通过读取文档快速了解上下文。

3. 避免"两个 AI 打起来"

有时候两个工具会给出一致的建议,那挺好;但有时候它们意见不一致,甚至互相否定对方的方案。

我的处理方式是:听更了解项目上下文那个的

比如这个模块是我用 Cursor 写的,Cursor 更清楚细节,那就以 Cursor 为主。反之亦然。


五、最后

说了这么多,其实就想说一点:工具是手段,不是目的

不管你用 Cursor 还是 Claude Code,还是两个组合用,最重要的还是解决实际问题。不要为了用工具而用工具,也不要因为某个工具火就跟风。

找到最适合你自己的工作流,让它为你服务——这才是正经事。

也欢迎关注我的公众号「清汤饺子」,获取更多技术干货!

01-想做 Code Agent,但不想只会调 API?我把 Claude Code 源码拆成了一套教程

关键词:Code Agent / Claude Code / CLI / Bootstrap / QueryEngine / Agent 架构 / 工程设计

别把 Code Agent 当聊天机器人:先看懂 Claude Code 的总架构和启动链路

很多人分析 Code Agent,一上来就盯着模型调用,结果越看越碎。

真正的问题不是“它调了哪个模型”,而是这套系统怎么从一个 CLI 命令,变成一个能长期执行任务的 Agent。Claude Code 的前两章其实就在回答这件事:一个 Code Agent 的骨架到底长什么样,它又是怎么被启动起来的。

一、先把范式分清:Chatbot、Copilot、Agent 不是同一种东西

从工程上看,这三者的差异不在 UI,而在执行边界。

类型 能做什么 不能做什么
Chatbot 一问一答、生成文本 不主动行动
Copilot 读编辑器上下文、给建议 通常不闭环执行
Code Agent 调工具、看结果、继续推进 不能没有状态机

Code Agent 的本质不是“更强的聊天”,而是下面这条循环:

flowchart TD
    U["用户输入"] --> C["组装上下文"]
    C --> M["调用模型"]
    M --> R{"返回类型"}
    R --> |"最终回答"| END["结束"]
    R --> |"tool_use"| T["调用工具"]
    T --> TR["工具结果回写历史"]
    TR --> M

只要系统进入这个闭环,它就不再是 Chatbot,而是执行器。

二、Claude Code 的总架构,其实是七块东西咬在一起

把源码抽掉细节,Claude Code 的主骨架是这样的:

CLI / Bootstrap
    ↓
QueryEngine / queryLoop
    ├─ Context Management
    ├─ Tool System
    ├─ Permissions / Hooks
    ├─ Skills / Plugins / MCP
    └─ UI Layer (Ink/React)

这里真正的主干不是 UI,也不是模型 SDK,而是中间三层:

  • queryLoop:负责让任务一轮轮继续;
  • Context:负责让模型每一轮都知道自己处在什么状态;
  • Tool System:负责把模型意图变成真实操作。

换句话说,Claude Code 不是“终端里包了一个 LLM”,而是“用 LLM 驱动的一套工具执行框架”。

三、CLI 启动的第一原则:快路径不能被慢路径拖累

Claude Code 在 cli.tsx 里先做了快速路径分流。像 --version 这种命令,根本不值得把主系统拉起来:

async function main(): Promise<void> {
  const args = process.argv.slice(2);

  if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
    console.log(`${MACRO.VERSION} (Claude Code)`);
    return;
  }
}

这个做法看起来普通,但它代表一个很成熟的 CLI 判断:

非主路径必须延迟加载,不能污染主路径的冷启动时间。

所以内部 MCP 模式、daemon worker、后台会话管理等路径,全部走动态 import()
不需要的模块,不在普通交互场景里承担启动成本。

四、main.tsx 做得最好的地方,不是功能多,而是“等待重叠”

进入 main.tsx 后,Claude Code 立刻做三件事:

import { profileCheckpoint } from './utils/startupProfiler.js';
profileCheckpoint('main_tsx_entry');

import { startMdmRawRead } from './utils/settings/mdm/rawRead.js';
startMdmRawRead();

import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js';
startKeychainPrefetch();

这意味着:

  • 入口开始时先打性能点;
  • 企业配置读取立即启动;
  • Keychain 中的 token / API key 预取立即启动;
  • 后续模块加载继续往下跑。
sequenceDiagram
    participant M as main.tsx
    participant K as Keychain
    participant L as 模块加载

    M->>K: startKeychainPrefetch()
    M->>L: 继续加载 Ink / Commander / React
    par 并行
      K-->>K: 读取凭证
      L-->>L: 模块求值
    end

它不是在做复杂优化,而是在贯彻一个很基本的工程原则:把等待和计算重叠起来
CLI 工具的启动体验,往往就输赢在这种细节上。

五、执行模式必须尽早判断:交互式和无头模式根本不是一回事

Claude Code 很早就判断当前是不是非交互模式:

const hasPrintFlag = cliArgs.includes('-p') || cliArgs.includes('--print');
const isNonInteractive =
  hasPrintFlag || hasInitOnlyFlag || hasSdkUrl || !process.stdout.isTTY;

关键点不是 -p,而是这一句:

!process.stdout.isTTY

这意味着只要输出不是终端,比如:

  • 被管道消费;
  • 被重定向到文件;
  • 跑在 CI 里;

它就自动转成非交互模式。

这才是一个能被脚本和流水线真正利用的 Agent CLI。
如果一个 Agent 只能服务“人在终端前手动敲命令”的场景,它的工程价值会被大幅限制。

六、参数解析不是装饰,它定义了系统有多少种工作姿态

main.tsx 里用 Commander.js 注册了大量参数。重要的不是“参数多”,而是参数直接映射系统姿态:

  • --print:无头执行;
  • --bare:跳过 hooks、LSP、插件同步等附加能力;
  • --permission-mode:切换权限模型;
  • --model / --effort:改变推理策略;
  • --allowed-tools / --disallowed-tools:收紧或放宽工具池;
  • --add-dir:调整文件可访问范围。

这说明 Claude Code 不是只有一种运行方式。
它是同一套核心循环,在不同环境里切换不同外壳。

七、真正的入口不是 main(),而是 query()

CLI 解决的是“怎么启动”,真正决定 Agent 行为的是 src/query.ts 里的 query()

export async function* query(
  params: QueryParams,
): AsyncGenerator<StreamEvent | RequestStartEvent | Message | ...> {
  const consumedCommandUuids: string[] = []
  const terminal = yield* queryLoop(params, consumedCommandUuids)
  for (const uuid of consumedCommandUuids) {
    notifyCommandLifecycle(uuid, 'completed')
  }
  return terminal
}

最值得注意的是它为什么是 async function*

因为 Agent 不是“跑完再返回”的程序,而是会在过程中持续产生事件:

  • 模型输出;
  • 工具调用;
  • 工具执行进度;
  • 工具结果;
  • 结束原因。

如果不用 async generator,就很难同时做好实时 UI 和中间状态分发。

八、queryLoop() 是整套系统的心跳

真正执行循环的是 queryLoop()。它维护一份跨轮次状态:

type State = {
  messages: Message[]
  toolUseContext: ToolUseContext
  autoCompactTracking: AutoCompactTrackingState | undefined
  maxOutputTokensRecoveryCount: number
  turnCount: number
  transition: Continue | undefined
}

这份状态告诉我们,Claude Code 从来不是“多调几次模型”。
它是一个明确的状态推进器。

每轮循环都做这几件事:

  1. 取当前消息;
  2. 检查是否需要压缩;
  3. 调模型;
  4. 看到 tool_use 就派发工具;
  5. 收集结果回写历史;
  6. 判断是否继续。
flowchart TD
    A["准备消息"] --> B["必要时压缩上下文"]
    B --> C["调用模型"]
    C --> D{"出现 tool_use?"}
    D --> |"是"| E["执行工具"]
    E --> F["工具结果回写 messages"]
    F --> A
    D --> |"否"| G{"是否完成?"}
    G --> |"是"| H["返回 Terminal"]
    G --> |"否"| A

只要你理解了这一步,就会知道为什么很多 Agent Demo 看起来会动,但一进真实任务就塌:
它们没有把“任务推进”做成状态机,只是做成了多轮问答。

九、第一篇该记住的,不是某个函数,而是三条原则

Claude Code 的前两章合起来,核心其实只有三条:

1. Agent 不是聊天产品,而是执行系统

只要它进入“调用工具-观察结果-继续决策”的闭环,它就不是普通聊天。

2. 启动路径本身就是工程能力的一部分

快路径分流、并行预热、模式早判,这些都不是边角优化,而是主能力。

3. 一切最终都收敛到 queryLoop 这个状态推进器

CLI、参数、模式、上下文、工具,最后都只是为了让这条循环稳定工作。

最后

如果把 Claude Code 当成“会写代码的聊天机器人”,后面很多设计你都会看不懂。
只有把它当成一个长期运行的执行框架,你才会理解:

  • 为什么启动要这么抠延迟;
  • 为什么模式要这么早分流;
  • 为什么 query() 要做成 async generator;
  • 为什么整个系统要围绕 queryLoop 组织。

这才是看 Claude Code 前两章最该拿到的东西。

Node.js 从入门到上线实战指南(零基础 → 高手)

覆盖:Node 基础、ES6 模块化、npm、Express、MongoDB、项目实战、部署上线、HTTPS 证书配置


一、什么是 Node.js?

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,让 JS 能运行在服务器端。

核心特点

  • 单线程 + 事件循环(Event Loop)
  • 非阻塞 I/O(高并发)
  • npm 生态强大

二、环境准备

1. 安装 Node.js

官网下载安装(LTS版本)

node -v
npm -v

三、Node.js 基础(零基础)

1. 第一个程序

console.log("Hello Node.js");

运行:

node app.js

2. 内置模块

const fs = require("fs");

fs.readFile("test.txt", "utf-8", (err, data) => {
  console.log(data);
});

常用模块:

  • fs(文件)
  • path(路径)
  • http(服务)

3. 创建 HTTP 服务(原生)

const http = require("http");

http.createServer((req, res) => {
  res.end("hello world");
}).listen(3000);

四、ES6 模块化(现代写法)

1. 开启模块化

{
  "type": "module"
}

2. 使用 import/export

// math.js
export const add = (a, b) => a + b;

// app.js
import { add } from "./math.js";
console.log(add(1, 2));

五、npm(包管理)

1. 初始化项目

npm init -y

2. 安装依赖

npm install express
npm install -D nodemon

3. scripts 脚本

"scripts": {
  "dev": "nodemon app.js",
  "start": "node app.js"
}

六、Express 框架(重点)

1. 快速启动服务

import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.send("Hello Express");
});

app.listen(3000);

2. 路由系统

app.get("/user", (req, res) => {
  res.json({ name: "Tom" });
});

3. 静态资源

app.use(express.static("public"));

4. 中间件

app.use(express.json());

app.use((req, res, next) => {
  console.log("请求来了");
  next();
});

七、MongoDB 数据库

1. 启动 MongoDB(Docker 推荐)

docker run -d -p 27017:27017 mongo

2. 使用 mongoose

npm install mongoose

3. 连接数据库

import mongoose from "mongoose";

mongoose.connect("mongodb://localhost:27017/test");

4. 定义模型

const UserSchema = new mongoose.Schema({
  name: String,
  age: Number
});

const User = mongoose.model("User", UserSchema);

5. CRUD

// 新增
await User.create({ name: "Tom", age: 20 });

// 查询
const users = await User.find();

八、项目实战(REST API)

1. 项目结构

project/
├── app.js
├── routes/
├── models/
├── public/

2. 示例 API

app.get("/api/users", async (req, res) => {
  const users = await User.find();
  res.json(users);
});

九、项目部署上线


1. Linux 服务器准备

sudo apt update
sudo apt install nodejs npm

2. 使用 PM2(进程管理)

npm install -g pm2
pm2 start app.js
pm2 save
pm2 startup

3. 使用 Nginx 反向代理

server {
  listen 80;

  location / {
    proxy_pass http://localhost:3000;
  }
}

十、HTTPS 证书配置(重点)

使用 Let's Encrypt 免费证书


1. 安装 certbot

sudo apt install certbot python3-certbot-nginx

2. 申请证书

sudo certbot --nginx

3. 自动续期

certbot renew --dry-run

十一、生产优化

1. 环境变量

NODE_ENV=production

2. 日志管理

使用 winston / morgan


3. 安全

npm install helmet

4. 跨域

import cors from "cors";
app.use(cors());

十二、总结

从零到上线你学会了:

  • Node.js 基础
  • ES6 模块化
  • npm 管理
  • Express 框架
  • MongoDB 数据库
  • REST API
  • PM2 部署
  • Nginx 反向代理
  • HTTPS 证书

🚀 进阶方向

  • 微服务架构
  • Docker 容器化
  • K8s 部署
  • GraphQL
  • WebSocket 实时通信

🚀 进阶方向(从工程到架构)

当你掌握了 Node.js + Express + MongoDB 的基础后,下一步要从“能写接口”进阶到“能做系统架构”。


一、微服务架构(Microservices)

📌 什么是微服务?

把一个大型系统拆分成多个小服务,每个服务独立运行:

用户服务  →  登录 / 注册
订单服务  →  下单 / 支付
商品服务  →  商品管理

🧱 Node.js 实现方式

最简单通信方式(HTTP):

// user-service
app.get("/user", (req, res) => {
  res.json({ id: 1, name: "Tom" });
});

// order-service 调用 user-service
import axios from "axios";

const user = await axios.get("http://localhost:3001/user");

🚀 常用技术

  • API Gateway(网关)
  • 服务注册发现(Consul / Nacos)
  • 消息队列(RabbitMQ / Kafka)

🎯 优点

  • 可独立部署
  • 高扩展性
  • 团队协作友好

二、Docker 容器化

📌 为什么要用 Docker?

解决“我本地能跑,你那跑不了”的问题


🐳 Dockerfile 示例

FROM node:18

WORKDIR /app

COPY . .

RUN npm install

EXPOSE 3000

CMD ["node", "app.js"]

▶️ 构建运行

docker build -t my-node-app .
docker run -p 3000:3000 my-node-app

📦 docker-compose(推荐)

version: "3"
services:
  app:
    build: .
    ports:
      - "3000:3000"
  mongo:
    image: mongo

🎯 优点

  • 环境一致
  • 快速部署
  • 易扩展

三、Kubernetes(K8s)部署

📌 是什么?

容器编排平台,用来管理大量 Docker 容器


🧱 Deployment 示例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: node
  template:
    metadata:
      labels:
        app: node
    spec:
      containers:
        - name: node-app
          image: my-node-app
          ports:
            - containerPort: 3000

🌐 Service 暴露服务

apiVersion: v1
kind: Service
metadata:
  name: node-service
spec:
  type: NodePort
  selector:
    app: node
  ports:
    - port: 3000
      targetPort: 3000

🎯 能力

  • 自动扩容(HPA)
  • 服务发现
  • 灰度发布

四、GraphQL(替代 REST)

📌 为什么用 GraphQL?

客户端可以“按需获取数据”


🚀 Node 示例

npm install graphql express-graphql
import { graphqlHTTP } from "express-graphql";
import { buildSchema } from "graphql";

const schema = buildSchema(`
  type Query {
    hello: String
  }
`);

const root = {
  hello: () => "Hello GraphQL"
};

app.use("/graphql", graphqlHTTP({
  schema,
  rootValue: root,
  graphiql: true
}));

🎯 优点

  • 减少接口数量
  • 前端更灵活
  • 强类型

五、WebSocket 实时通信

📌 应用场景

  • 聊天系统
  • 实时通知
  • 在线游戏

🚀 Node 实现(ws)

npm install ws
import { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (ws) => {
  ws.send("连接成功");

  ws.on("message", (msg) => {
    console.log("收到:", msg.toString());
  });
});

🎯 特点

  • 双向通信
  • 实时性强
  • 比 HTTP 更高效

🧠 进阶学习路线总结

Node基础
   ↓
Express + MongoDB
   ↓
REST API
   ↓
Docker(容器化)
   ↓
微服务架构
   ↓
K8s(集群部署)
   ↓
GraphQL / WebSocket(高级能力)

🎯 最终目标

👉 从“写接口”升级为:

  • ✔ 架构设计能力
  • ✔ 分布式系统思维
  • ✔ 可扩展系统构建能力

🚀 一句话总结

真正的 Node.js 高手,不只是会写接口,而是能设计系统。


一天时间,用 Claude Code 蹬了一个 v0 出来(附源码)

最近,出于业务需要,参考 v0 的实现,蹬了一个类似 v0 的平台出来。

先看效果:

整体采用 Next.js 做前后端服务,E2B 提供沙箱,Claude Agent SDK 完成代码生成,沙箱提供预览和代码推送部署能力。

ps: 本文不会包含任何的代码(本身也都是 AI 生成的),只会介绍相关方案的选型、核心的架构和实现原理。同时关于部署的环节,各个公司都有自己的部署流水线,并不具备参考价值,会弱化这个环节的介绍。

方案对比和设计

AI 生成前端代码,一般有这么几种方式:一份 html,一份代码块,以及直接生成项目。

生成 html

生成一份 html,然后增删改查,最终存储 html 即可,不论是预览还是部署,都最为简单。

有很多产品都是这么做的,比如 Claude 的 Artifacts,Google 的 Stitch。

这是最简单,也最轻便的方案。

这里面的关键技术点有几个:

  1. 如何让 AI 生成高质量的 HTML?当然这也无非就是需要一些非常优秀的提示词来约束 AI 的行为。

  2. 如何增量修改?通过在浏览器侧实现一个支持局部替换的 Edit Tool 即可,这也是很多 cli 工具在本地修改代码的常见策略。

  3. 后期的可维护性是这个方案最大的隐患。生成的 HTML 往往是一个几百行甚至上千行的单文件,没有组件拆分,没有模块化,样式和逻辑全部混在一起。如果需要人工介入修改,多年程序员看到这样的代码,大概会有一种被拉回刀耕火种时代的感觉——能改,但很痛苦。这也意味着,一旦走上这条路,后续的迭代就只能继续依赖 AI,项目实际上已经不再适合人来维护。

ps: 这里可能会有人好奇,为什么不是修改某一行某几列的代码,这是因为 AI 对于行号识别不准确,反而直接执行字符搜索并替换更为准确。感兴趣的可以查看 pi-mono 项目中 edit 工具的实现,这也是绝大部分 cli 工具的实现方案。

至于 html 的预览和部署,可谓是极为简单且花费最少了。

生成代码块

另一种方式是:生成代码块,存储在数据库中,预览采用 WebContainer、Sandpack,或者通过 Babel 转 CommonJS 在浏览器端模拟打包等方式来预览前端项目。

这基本是纯前端的方案,不过 WebContainer 要授权,Sandpack 倒是开源,但是加载速度上可能存在一些问题。至于 Babel 转 CommonJS 自行实现编译系统,也是 ok 的,只是要支持 jsx, vue, 要花一点时间,开发的工作量不小。

当然,除了这些建设,如何稳定 AI 的输出,也是这个方案中的一大问题,理想情况下,希望 AI 的产物是 文件名 + 内容 组合成的 json 数组。

一般可以通过几个方案来解决:

  1. 换更好的模型
  2. 运用 XML 这样的提示词技巧,来让 AI 输出的更符合预期

但是这个方案有几个比较大的问题:

  1. 编译工程复杂度比较高
  2. 增量替换的方案,输出格式可能不如工具调用那般精准,在耗时和质量上会更低效一点。
  3. 对于外部依赖的包,需要提前做编译、告知 AI 用法等,相对不那么自由

直接生成项目

直接生成项目,最终预览和部署都和普通的项目一样。这也是 v0 的方案。

这个方案本质上是给用户准备一个沙箱,这个沙箱中,直接启动一个 claude code 或者 codex 这样的工具,可以是 cli 也可以是 sdk。

同时指定一个工作目录,最终的项目生成和运行,都发生在这个工作目录下。用户输入直接指向 claude code,从而完成项目的生成。

这个方案的灵活度最高,同时由于背后是最顶尖的 AI 生成工具,所以在质量上和效率上,其实都不太需要担心。

但是最大的问题就在于需要给每一个用户都提供一个沙箱,对于运维部署的能力要求比较高。

同时沙箱的内存分配和 cpu 分配,资源上也不能少。

不过好在已经有很多服务商提供这样的服务,比如 E2B、Cloudflare 等服务商。付费调 API 的话,准备一个沙箱也很容易。

对比表格

维度 生成 HTML 生成代码块 直接生成项目
实现复杂度
预览方案 直接渲染 iframe WebContainer / Sandpack / Babel 转 CommonJS 沙箱内启动 dev server
部署复杂度 极低,存 HTML 即可 低,纯前端方案 高,需要为每个用户分配沙箱
增量修改精准度 高(字符串 Edit Tool) 中(输出格式不如工具调用稳定) 高(Agent SDK 原生工具调用)
AI 输出稳定性 高(单文件,约束简单) 中(需要结构化 JSON 输出,依赖提示词技巧) 高(由 Agent 工具链保证)
外部依赖支持 弱(只能用 CDN 引入) 弱(需要提前编译、告知 AI 用法) 强(npm install 自由安装)
代码可维护性 低(不适合人工维护) 高(标准项目结构)
资源消耗 极低 高(沙箱需要分配内存和 CPU)
灵活度
代表产品 Claude Artifacts、Google Stitch Bolt.new(基于 StackBlitz WebContainer) v0、本文实现

架构设计

整体的架构图如上,分为三块:

  1. Next.js 前端:聊天输入框、消息流展示、代码文件树、实时预览 iframe,以及打断/重试等交互控制。

  2. Next.js 后端:接收前端消息,维护会话与沙箱的映射关系,将消息转发给对应沙箱内的 Agent,并将 Agent 的流式输出透传回前端。

  3. E2B 沙箱:基于自定义模板启动,模板内预装了 Node.js 环境和项目脚手架。沙箱内运行 Claude Agent SDK,负责代码的生成与修改;同时启动 dev server 并通过 E2B 的端口暴露能力对外提供预览。

消息流转

用户操作路径如下:

  1. 用户打开平台,发起第一条消息,后端按需创建 E2B 沙箱(冷启动约需几秒)
  2. 沙箱就绪后,后端将消息投递给沙箱内的 Claude Agent SDK
  3. Agent SDK 开始工作:调用文件读写工具生成或修改代码
  4. Agent 的输出以流式事件的形式,经后端透传回前端实时展示
  5. 代码变更同步到文件树,预览 iframe 直接加载沙箱暴露的端口

会话与沙箱管理

多用户场景下,每个会话对应一个独立的沙箱实例,隔离性天然满足。

上下文的维护完全交给 Agent SDK,后端只需持久化"会话 ID → 沙箱 ID"的映射即可。考虑到沙箱有闲置超时机制,需要在映射层做好沙箱的重建和恢复逻辑,一般沙箱的服务方基本都会内置这些能力。

部署发布

代码的部署和发布,一个比较通用的方案是在沙箱内完成 Git 提交,推送到远程仓库后触发 CI/CD 流水线,从而完成项目的上线。由于这部分强依赖各公司自身的发布体系,本文不展开。

整体来讲技术卡点并不多。最核心的 AI 代码生成能力,借助 Agent SDK 即可完成,质量和直接使用 Claude Code 打平。沙箱管理和前端页面反而是 AI 最擅长的部分,蹬起来毫无压力。

心得体会

整体蹬一个 v0,让 AI 写代码花费的时间其实并不多,大概一天左右就能蹬出来。

但是有一说一,这个方案,其实来来回回跟 AI 拉扯了几天,大到从生成 HTML,到生成片段代码,再到最后的沙箱方案,而小到增量更新的解决方案,Babel 转义的优劣,都属于考量的范畴。

包括是用 Agent SDK,还是直接用 Claude CLI,也是经过多方权衡后的结果。

一切方案落定,Plan Mode 开启,Opus 一开,反而是最轻松的时刻。

基本上第一次的产物,就能达到最小 demo 的效果。

至于交互上的细节,比如打断输入,补充说明,向用户提问明确需求,这些细节上的打磨,也是花点心思就能解决的地方。

整体来讲,在没有 AI 介入之前,其实是不太能这么快完成这样一个系统的。单单是沙箱方案的选型,可能都要花费个几天,比如沙箱的暂停和恢复,费用的对比等等,也是 AI 辅助决策的结果,有了决策,实现又是几天,确确实实在效率上提升非常大。

在这个过程中,我本身也是直接退订了 Cursor,因为完全不需要自己再上手手动修代码了,单说执行这块,AI 绝对是夯爆了。

很难说不焦虑,但又感觉不必太过焦虑。这次最大的体感不是"AI 写代码很快",而是整个过程中,花时间最多的地方依然是人在做的事——判断方案的取舍,理解各种工具的边界,决定什么值得做、什么可以砍掉。执行层 AI 确实夯爆了,但执行之前的那些决策,AI 只是参谋,拍板的还得是人。

所以与其焦虑被替代,不如想清楚自己在一件事里到底在做什么。毕竟 AI 还是得有人蹬,至于蹬到哪里去,这个问题 AI 替你答不了。

源码

本文的 POC(Proof of Concept,概念验证)代码已开源,即用最小的实现跑通"用户输入 → Agent 生成代码 → 沙箱预览"这条核心流程,感兴趣的可以查看:github.com/yuzai/code-…

省市区县乡镇街道三级四级联动数据源:2026年民政部和统计局已不再公布行政区划代码,可改用直接调国家地名信息库的接口

国家地名信息库

国家地名信息库链接: dmfw.mca.gov.cn

接口服务文档:dmfw.mca.gov.cn/interface.h…

正常程序中使用时,将数据缓存起来存入文件或者数据库,比调用接口更稳定。

//直接在浏览器控制台执行,可以测试接口结果
/* 接口说明
code不填时获取省级,填了时获取对应的区划和下级数据
maxLevel=1只获取一级数据 maxLevel=2获取两级数据 maxLevel=3获取三级数据
*/
//获取全国省市区三级,将近300KB数据接口会比较慢
var response=await fetch("https://dmfw.mca.gov.cn/9095/xzqh/getList?code=&maxLevel=3");
var data=await response.json();
console.log(data);

//只获取获取省级数据,速度比较快
var response=await fetch("https://dmfw.mca.gov.cn/9095/xzqh/getList?code=&maxLevel=1");
var data=await response.json();
console.log(data);

//获取湖北省 省、市、区 三级
var response=await fetch("https://dmfw.mca.gov.cn/9095/xzqh/getList?code=420000000000&maxLevel=3");
var data=await response.json();
console.log(data);

//获取武汉市 市、区县、乡镇街道 三级
var response=await fetch("https://dmfw.mca.gov.cn/9095/xzqh/getList?code=420100000000&maxLevel=3");
var data=await response.json();
console.log(data);

注意:当直接获取省市区三级数据时,以下城市只有两级:

  1. 直辖市(如:北京、天津、上海、重庆)
  2. 不设区的市(如:东莞、中山、儋州、嘉峪关)
  3. 省直辖县级行政单位(如:济源、仙桃、琼海、胡杨河)

其他省市都有三级结构。

数据信息

统计局自2024年下半年起就不再公开统计用区划代码,改用国家地名信息库数据。

民政部公告相关链接:www.mca.gov.cn/n156/n186/i…

摘自民政部的公告:自2026年起,本栏目不再公布行政区划代码相关信息。请前往民政部门户网站首页的国家地名信息库版块查询相关信息。

已整合的开源库:github.com/xiangyuecn/…

开源库:已将四级数据整合到了单个csv文件中,同时提供标注拼音、坐标和四级边界范围。提供工具生成多级联动数据和代码,也支持将数据导入MySQL、MSSQL、PgSQL、Oracle等数据库中

【2026-04-03】国家地名信息库行政区划数据截止日期为2025年12月31日。

最近爆火的 Harness Engineering 被我提炼成了 SKILL,小白也能快速上手

✨文章摘要(AI生成)

笔者分享了将 Harness Engineering 知识提炼为可复用 Agent Skill 的经验。在系统阅读了 Anthropic、OpenAI、Martin Fowler、LangChain 等来源的文章后,提炼出 Harness 设计的七个核心层:项目搭建、上下文工程、约束与防护、多 Agent 架构、评估与反馈、长时间任务、诊断。最终产出的 harness-engineering 技能覆盖三大场景——新项目搭建、Agent 行为诊断、持续改进,采用渐进式披露架构。定量评估显示有技能时断言通过率 100%,无技能时 83%。核心洞察:Agent 表现不好,80% 的原因不在模型,在 Harness。

为什么写这个

最近两年,笔者在使用各种AI编码助手(Claude Code、Cursor、Copilot等)的过程中,反复遇到一个问题:Agent时好时坏,虽然整体来说随着模型能力进步是向好的,但是向好的过程是曲折波动的。

有时候它写的代码完美契合项目风格,有时候它像个第一天入职的实习生——不知道项目结构、不遵守约定、还把之前商量好的决策忘得一干二净。

然后开始从 Prompt Engineering 中使用结构化、few shot、few example 等技巧,来让 AI 的输出更加稳定。 后面又使用 Context Engineering 来让 Agent 的上下文更加丰富,来让 Agent 的表现更加稳定。

最近几周,一个更系统的词汇出现了:Harness Engineering。

Agent表现不好,80%的原因不在模型,在Harness。 - Anthropic

什么是Harness?简单说:

  • 模型 = CPU(算力本身)
  • 上下文窗口 = RAM(工作记忆)
  • Harness = 操作系统(调度、约束、反馈、文件系统——一切让CPU有效工作的基础设施)

你不会指望一个CPU在没有操作系统的裸机上高效运行。同理,你也不该指望一个模型在没有Harness的项目里稳定输出。

我学到了什么

笔者系统阅读了以下来源的文章:

  • Anthropic — 构建高效Agent、多Agent研究系统、长时间运行Agent的Harness设计
  • OpenAI — AGENTS.md设计模式、Context Engineering最佳实践
  • Martin Fowler — Harness Engineering的工程哲学("Relocating Rigor")
  • LangChain — Agent框架 vs 运行时 vs Harness的分类学
  • philschmid — 2026年Agent Harness的重要性
  • 独立开发者实践 — Hermes Agent的自演化、Vue Lynx的设计笔记驱动开发
  • 学术论文 — 自然语言Agent Harness的形式化研究

读完之后,我发现这些文章虽然角度各异,但核心思想收敛到了七个层

层级 解决什么问题 一句话总结
项目搭建 Agent不知道项目是什么 AGENTS.md是目录,不是百科全书
上下文工程 Agent看到的信息不对 给地图,不给手册
约束与防护 Agent犯重复的错 每犯一次错,加一条规则
多Agent架构 单Agent搞不定复杂任务 分工明确,协议清晰
评估与反馈 不知道Agent做得好不好 让AI检查AI
长时间任务 Agent跑着跑着就走偏了 进度文件 + 上下文重置
诊断 用户骂Agent不好用 问题在Harness,不在模型

所以我做了个技能

读完这些文章,笔者意识到这些模式完全是可复用的。不管你的项目是React前端、Python后端还是Rust CLI工具——Harness的设计原则是通用的。

于是我把这些知识提炼成了一个 Agent Skill,名叫 harness-engineering

它做什么

这个技能有三个核心使用场景:

场景一:新项目搭建

当你启动一个新项目,告诉Agent"帮我搭建Harness工程",它会:

  1. 评估你的项目类型、技术栈、团队规模
  2. 创建 AGENTS.md(表of目录式的Agent导航文件)
  3. 建立 docs/ 目录(架构、约定、数据模型等)
  4. 配置约束层(lint规则、类型检查、pre-commit hooks)
  5. 设置评估与反馈机制

场景二:Agent表现不佳时的诊断

这是最有意思的场景。当你开始抱怨——

  • "它怎么又犯同样的错误?"
  • "它根本不遵守我们的约定!"
  • "它写的代码质量太差了"

这个技能会被触发,引导Agent去诊断Harness层的缺失,而不是怪模型:

你的抱怨 大概率原因 修复方式
总犯同一个错 没有约束阻止它 加一条lint规则
不遵守约定 约定没写下来或Agent找不到 写入docs/,在AGENTS.md中引用
忘记之前的决定 跨会话上下文未持久化 用progress.md记录决策
代码质量差 没有好代码的示例 在DESIGN_NOTES.md中加示例

场景三:持续改进

每次发现新的可复用Harness模式,更新到技能中,让它在其他项目中也能受益。

它怎么组织的

技能采用渐进式加载架构:

harness-engineering/
├── SKILL.md              # 入口文件(<60行),路由到具体参考文档
└── references/
    ├── 01-project-setup.md       # 项目搭建
    ├── 02-context-engineering.md  # 上下文工程
    ├── 03-constraints.md          # 约束与防护
    ├── 04-multi-agent.md          # 多Agent架构
    ├── 05-eval-feedback.md        # 评估与反馈
    ├── 06-long-running.md         # 长时间任务
    └── 07-diagnosis.md            # 诊断

SKILL.md本身非常精简——它就像一个路由器,根据当前场景指引Agent去读对应的参考文档。这遵循了Harness Engineering本身的原则:渐进式披露,按需加载

几个让我印象深刻的模式

有几个模式特别触动笔者,感同身受,这里单独拿出来聊聊。

"给地图,不给手册"

这个观点从推文中看到。传统做法是给Agent写详细的分步指令(手册),但这让Agent变得脆弱——任何偏差都会导致它不知所措。

更好的做法是给Agent一张地图

# 不好的写法(手册)
Step 1: 打开 src/auth/login.ts
Step 2: 找到 handleLogin 函数
Step 3: 在第42行添加...

# 好的写法(地图)
Auth系统在 src/auth/。登录流程:login.ts → validate.ts → session.ts。
限流中间件在 src/middleware/rateLimit.ts——参考它的模式。
每次修改auth都要在 src/auth/__tests__/ 里加测试。

地图让Agent能自主导航,手册让它成为脆弱的执行机器。

"每犯一次错,加一条规则"

这个模式来自多篇文章的交叉验证。核心思想:

  1. Agent犯了一个错
  2. 你修复了这个错
  3. 然后你加一条规则,永远阻止这类错再次发生

这条规则可以是lint规则、类型约束、测试用例,或者只是文档中的一条约定。随着时间推移,Harness积累了越来越多的规则,Agent的错误率对已知模式趋近于零。

这其实就是Martin Fowler说的 "Relocating Rigor"——把人类通过Code Review、经验、直觉实施的质量把关,迁移到自动化检查中。Agent在被检查的边界内自由运行。

Harness = 数据集

这个观点来自Anthropic。每次Agent交互都是一个训练信号:

  • 它尝试了什么
  • 什么成功了
  • 什么失败了
  • 修复方案是什么

这些痕迹(traces)就是你的竞争优势。它们是让你的Harness随时间越来越好的数据——不是微调模型,而是优化操作系统。

技能评估:有没有用?

笔者遵循skill-creator的流程,对这个技能做了定量评估。设计了3组测试场景,每组跑with-skill和without-skill两个版本:

测试场景 有技能 无技能
新项目搭建 6/6 ✅ 4/6
Agent行为诊断 6/6 ✅ 5/6
跨模块依赖问题 6/6 ✅ 6/6
合计 18/18 (100%) 15/18 (83%)

有技能的版本在所有场景下都通过了全部断言。无技能的版本在"新项目搭建"场景下缺失较多——它不知道要创建AGENTS.md、不知道docs/应该怎么组织、不会设置渐进式披露的上下文架构。

当然,17%的差距不算巨大。但关键是:有技能时Agent的输出一致且完整,无技能时看运气。对于一个工程实践类技能来说,一致性比偶尔的惊艳更有价值。

怎么安装

这个技能可通过 GitHub 安装:

npx skills add 10xChengTu/harness-engineering

安装后,当你在Claude Code、OpenCode或其他支持Skills的Agent中工作时:

  • 启动新项目 → 技能自动触发,引导搭建Harness
  • 遇到Agent质量问题 → 开始抱怨时技能会介入诊断
  • 主动询问 → "帮我改进这个项目的Harness"

最后

Harness Engineering目前还是一个非常早期的领域。模型在变强,今天需要的约束明天可能就多余了——所以这个技能本身也遵循一个核心原则:为删除而构建

如果你也在用AI Agent做开发,不妨试试给你的项目加上Harness。从最简单的开始——一个AGENTS.md文件、几条lint规则、一个progress.md。然后观察Agent的表现变化。

你大概率会和笔者有同样的感受:不是模型不行,是我们没给它一个好的工作环境。

本文涉及的所有参考文章和完整技能源码,均可在GitHub 仓库中找到。

连载03-commands ---一起吃透 Claude Code,告别 AI coding 迷茫

为什么 Claude Code 要有指令(Commands):本质是上下文管理

AI Coding 系列第 03 篇 · 上下文与 Commands


这篇想回答三个问题:

  1. 为什么一开始很好用的 Claude Code,会在长会话里越来越"跑偏"?
  2. 为什么有些纠正你明明说过很多次,它还是会回退?
  3. 为什么 Claude Code 要用 /clear/compact/memory 这种命令,而不是全做成按钮?

如果你已经是高频用户,这篇不会提供很多新奇技巧,但会把这些现象背后的机制串成一套可操作的框架。对刚进入稳定开发阶段的用户,这比记住几个命令更重要。


先给结论

Claude Code 里的很多命令,看起来像快捷操作,实际上是在帮你管理三个东西:

  1. Claude 现在看到了什么
  2. Claude 接下来应该忘掉什么
  3. 哪些规则不应该继续留在“会衰减的对话历史”里

如果只把 /clear/compact/memory 当成“方便一点的小功能”,你会低估它们的价值。它们真正解决的,是长会话里最常见、也最让人误判的协作问题:Claude 不是突然变差了,而是上下文开始劣化了。


你大概遇到过这种情况

开始一个新任务,头几轮对话质量很高——Claude 理解你的意图,给出准确的方案,代码风格和项目一致。

但聊着聊着,情况变了:它开始重提已经否掉的方案,重复犯你纠正过的错误,或者在一个局部问题上越绕越深,忘了你们最初要解决什么。

这不是 Claude 变笨了,也不是你的 Prompt 写差了。这是上下文劣化——一个有规律、可以预测、也可以干预的现象。


一个贯穿全文的真实场景

假设你在做一个登录模块,从头到尾大概会经历这样一条线:

  1. 前期你和 Claude 一起讨论技术方案,聊过 Session、JWT、Redis、PostgreSQL,最后决定先用 PostgreSQL + JWT,把主流程跑通。
  2. 接着你开始写代码,发现 Claude 老是顺手加 console.log,你纠正了它两次。
  3. 做到一半你又决定把 ORM 从 Sequelize 换成 Prisma,因为前者在这个项目里太重。
  4. 再往后,对话已经很长了,你只是问一个事务边界问题,Claude 却开始同时聊缓存、日志、前端错误提示和部署建议。

如果你不理解上下文管理,这整条链看起来像是:“Claude 前面挺聪明,后面越来越不靠谱。”

但如果从上下文角度看,这其实是四类不同问题叠在一起:

  • 前面讨论过但已经否掉的方案,还残留在历史里
  • 你纠正过的规则,还停留在会衰减的对话层
  • 已经废弃的 Sequelize 路线,还在干扰后续回答
  • 对话长度过大以后,Claude 的注意力开始发散

也正因为它们是四类不同问题,所以不能指望一个万能 Prompt 解决。你需要的不是“再说清楚一点”,而是把正确的动作放在正确的层级上:该 /compact 的时候压缩,该 /clear 的时候重开,该进 CLAUDE.md 的规则就不要继续留在聊天记录里。


为什么 Claude Code 用命令,而不是全做成按钮

刚接触 Claude Code 的人常问:为什么不把这些能力做成 GUI?为什么要用 /clear/compact 这种命令?

因为 Claude Code 管理的不是"功能开关",而是 Claude 此刻正在处理的内容。

举个对比:你在 VS Code 里点"开启 dark mode",改的是软件的配置文件,和编辑器当前打开了什么文件没关系。但你在 Claude Code 里输入 /clear,清掉的是 Claude 脑子里正在处理的所有东西——之前讨论过的方案、做过的决策、来来覆去的对话,全部抹掉,让它重新认识你。这不是改配置,是在决定 Claude 此刻"知道什么、记得什么"。

命令形式在这里有几个优势:它可重复,你可以稳定复现同一种操作;它可传达,你可以直接告诉同事"先 /clear 再开始";它也更容易固化成工作流,比如"任务切换先 /clear,长会话中途 /compact,提交前 /review"。对还在快速迭代的 AI 工具来说,命令也比 GUI 更容易快速交付新能力。

所以 Claude Code 的命令系统,本质上是一套让你主动管理 AI 工作现场的操控面板


四种上下文劣化模式

Claude Code 的 4 种上下文劣化转存失败,建议直接上传图片文件

模式一:早期探索污染后期决策

任务开始时,你和 Claude 在讨论方案,思路是发散的。"要不要用 Redis?""用 JWT 还是 Session?""这个表要不要拆?"——这些都是探索性的讨论,很多想法最终被否决了。

问题是:这些被否决的想法还留在上下文里,和最终确定的方案并排存在。Claude 看到的是一串讨论记录,它会对"用 Redis"和"不用 Redis"这两种可能性都保持某种权重。对话越长,早期探索的内容就越像已确认的决策。

例如:

你:认证这块先别上 Redis,先用 PostgreSQL 把主流程跑通。
Claude:好,先按 PostgreSQL 方案实现。
...
(十几轮后)
Claude:为了提高性能,建议把 session 放到 Redis。

你明明在第 5 轮确认了用 PostgreSQL,第 25 轮它开始建议你考虑一下 Redis 的方案。

模式二:纠正回退

你发现 Claude 用错了某个写法,纠正了它。它承认了,改对了。五轮之后,它犯了同样的错误。

这是因为"纠正"发生在对话的第 8 轮,而你现在在第 15 轮。纠正的内容在上下文里的位置越来越靠后,在 lost-in-the-middle 的注意力分布下,它的权重持续降低,直到 Claude 实际上已经"忘了"那次纠正,重新回到了训练数据里的默认行为。

例如:

你:不要用 console.log,用 logger.info。
Claude:好的,我改成 logger.info。
...
(五轮后)
Claude:这里我先加一个 console.log 方便排查。

你已经说了三次"不要用 console.log,用 logger.info",但它还是偶尔会写 console.log

模式三:废弃方案的幽灵

你尝试了一种实现方式,做到一半发现不对,放弃了,换了另一种方式。旧的代码删掉了,但关于旧方案的讨论还留在上下文里。

这段"废弃的历史"会持续影响 Claude 的输出——它可能在新方案里混入旧方案的逻辑,或者在你遇到问题时建议你回到旧方案,因为旧方案在它的上下文里看起来也是一个"被讨论过的合理选项"。

例如:

你:Sequelize 这条路不走了,切到 Prisma。
Claude:明白,后续都按 Prisma 来。
...
(后来你问一个查询问题)
Claude:你可以在 Sequelize 的 include 里这样写...

你已经换掉了 ORM,但 Claude 还在参考旧 ORM 的写法给你示例。

模式四:注意力发散

随着上下文增长,Claude 需要处理的信息量越来越大。注意力是有限的——分给了 A 就少给了 B。越是长对话,Claude 越难在某一个具体问题上保持高度聚焦。它的回答开始变得面面俱到但不够深入,或者在你问一个具体问题时夹带了很多你没问的背景讨论。

例如:

你:只看这个接口的事务边界,哪里可能有问题?
Claude:这个接口本身有事务问题。另外认证模块、日志方案、缓存策略、
      前端错误提示也建议一起调整...

任务越来越复杂,但 Claude 的回答越来越泛,不够犀利。


理解了劣化,命令就有了意义

这四种模式,对应的干预手段是不同的:

劣化模式 干预手段 原因
早期探索污染 /compact 明确声明保留哪些决策 压缩时主动过滤探索内容,只留结论
纠正回退 把纠正写进 CLAUDE.md 从"对话历史"变成"系统注入",每轮强制生效
废弃方案幽灵 /clear 重开一个干净会话 彻底清除废弃历史,而不是试图覆盖它
注意力发散 拆任务 + 每个任务独立会话 每个会话只有一个聚焦点

但这些命令本质上是不同类型的东西,可靠性和适用场景差别很大——这是大多数人没意识到的。


5 条最实用的决策规则

如果你只想带走最实用的部分,可以先记这 5 条:

  1. 任务已经切换,就先 /clear。不要让上一个任务的残留背景继续污染当前任务。
  2. 任务没切,但会话已经很长,就 /compact。而且最好写清楚“只保留哪些已确认决策”。
  3. 同一条规则纠正两次以上,就别再聊了,直接写进 CLAUDE.md
  4. 你要复用的是“提示词流程”,就做成自定义 command;你要复用的是“带权限约束的能力”,就做成 skill。
  5. 当 Claude 开始“什么都懂一点但什么都答不深”时,优先怀疑上下文过载,而不是先怀疑模型突然变差。

这 5 条的价值,在于它们能把“模糊感觉”迅速翻译成明确动作。真正影响协作质量的,不是你知不知道这些概念,而是你能不能在出现问题的当下做对动作。


Claude Code 命令的三种类型

Claude Code 的 3 类指令 / Commands转存失败,建议直接上传图片文件

第一类:CLI 状态操作命令

这些命令直接操作 Claude Code 进程的内部状态,不经过 AI 模型,执行的是确定性的代码逻辑。

/clear    → 直接清空内存里的对话历史数组
/cost     → 读取 token 计数器,格式化输出
/model    → 修改当前会话的模型配置
/memory   → 读取和展示 Memory 目录下的文件内容
/help     → 输出命令列表

关键特性:结果确定,不依赖 Claude 的理解。 /clear 必然清空,/cost 必然显示费用,不会因为你的 Prompt 写得好不好而有差异。这类命令是系统层面的操作,不是 AI 行为。

第二类:自定义 Slash Command(提示词模板)

这类命令存放在 .claude/commands/ 目录下,每个命令是一个 .md 文件。文件名就是命令名,文件内容就是命令触发时注入给 Claude 的提示词。

.claude/
  commands/
    review.md      → /review 命令
    pr-desc.md     → /pr-desc 命令
    standup.md     → /standup 命令

可以用 $ARGUMENTS 接收参数:

<!-- .claude/commands/review.md -->
对以下代码做专项 review,聚焦:$ARGUMENTS

检查顺序:
1. 安全漏洞(SQL 注入、权限校验缺失)
2. 边界条件和错误处理
3. 项目规范符合性(参考 CLAUDE.md)

每个问题标注严重等级:blocking / warning / suggestion

关键特性:本质上是一次对话,经过 AI 模型处理,结果有随机性。 你写的是提示词,不是程序——好的自定义命令写法和好的 Prompt 写法是一回事:具体、有约束、有明确的输出格式。

第三类:Skills(能力包触发)

Skills 比自定义命令更重,有完整的元数据配置:限制工具权限(allowed-tools)、指定触发条件(when_to_use)、选择模型(model)、设置上下文隔离(context: fork)。

---
name: security-audit
description: 安全审查
when_to_use: 审查代码安全漏洞时
allowed-tools:
  - Read
  - Grep
  - Glob
model: claude-opus-4-5
context: fork
---
 ${target} 执行安全审查...

Skills 触发时,会在隔离的子上下文里运行,工具权限是物理隔离的(不是靠 Claude 自律),结束后把结果返回主会话。

关键特性:比自定义命令更结构化,工具权限有硬约束,可以和主会话隔离运行。 适合封装有副作用、需要权限控制的操作。第 05 篇会专门讲 Skills 的设计。


三类命令的选用原则

需要确定性结果,不想靠 Claude 判断 → 第一类 CLI 命令。清空上下文、查费用、切模型,这些操作不该有歧义。

想复用一套工作流程,不需要特殊权限控制 → 第二类自定义命令。把反复用到的提示词结构固化下来,/standup/pr-desc/review 这类日常命令都适合。注意:它还是提示词,不是代码。

封装有风险的操作,或者需要隔离运行 → 第三类 Skills。权限隔离只有 Skills 能做到。


两个值得停下来想的洞见

什么时候该 /clear、/compact、改 CLAUDE.md转存失败,建议直接上传图片文件

把命令类型和劣化模式放在一起,有两个反直觉的结论。

洞见一:在对话里纠正 Claude 是徒劳的——这是机制决定的,不是你说得不够清楚

模式二"纠正回退"的根本原因不是 Claude 不配合,而是你在用错误的工具纠正它。

在对话里说"不要用 console.log",这条纠正被写进了"会随时间衰减的历史"——位置越来越靠后,注意力权重持续降低,最终必然回退。这不是偶然的,是 lost-in-the-middle 的机制决定的。

更重要的是,这件事其实很容易自己验证。你可以做一个小实验:

  1. 在纯对话里告诉 Claude:"不要用 console.log,用 logger.info。"
  2. 继续推进几轮任务,再让它生成新代码。
  3. 然后把同一条规则写进 CLAUDE.md,再重复一次类似流程。

大多数时候你会发现,两种方式的持久性差别非常明显。前者更容易回退,后者更稳定。这比单纯讲原理更有说服力,因为你能亲手看到规则所在层级不同,稳定性就不同。

真正有效的纠正只有一种:把规则从对话历史移进系统注入层。

# CLAUDE.md
- 日志统一用 logger.info/warn/error,禁止 console.log
- 所有异步函数必须有 try-catch,不依赖外层中间件捕获
- 禁止使用 any,类型必须明确

写进 CLAUDE.md 的规则,在每次对话开始时被系统自动注入,优先级高于对话历史,不会随对话长度衰减。这是 CLAUDE.md 存在的真实原因——不是"项目文档",是绕过对话历史衰减的唯一可靠手段

判断标准很简单:如果你对同一件事纠正了两次以上,就不该继续在对话里纠正,而应该把它写进 CLAUDE.md。

洞见二:/compact 不是无损压缩,它本身就是一次 AI 调用

很多人以为 /compact 是把历史"存档"了,实际上 Claude Code 在压缩时会调用模型生成摘要——这意味着压缩结果的质量,取决于 Claude 怎么理解这段历史。

这里不需要依赖源码猜。单从行为上你就能判断出来:/compact 不是简单的机械压缩,而是在"理解历史之后生成摘要"。

为什么这么说?因为如果它只是确定性的算法压缩,那么你补不补"保留说明",结果应该差异很小;但实际使用中,空着用和带明确保留说明用,摘要质量往往差很多。这更像是模型在根据你的提示重新组织历史,而不是程序在做无损归档。

你在 /compact 后面附加的保留说明,本质上就是在告诉 Claude:哪些内容应该成为压缩后的锚点。有没有写、写了什么,会直接影响压缩后的摘要长什么样。

这有两个实际含义:

第一,/compact 不在第一类"确定性命令"里——尽管它看起来是内置命令,但压缩结果是 AI 行为,不是代码行为,存在质量差异。

第二,空着用和带保留说明用,结果可以差很多:

❌ /compact
   → Claude 自己判断什么重要,探索性讨论和已确认决策同等对待

✅ /compact 只保留已确认的决策:JWT 方案、Prisma 数据库表结构、
            错误处理用 AppError 类。探索阶段被否决的方案不需要保留。
   → Claude 围绕这些锚点生成摘要,后续对话里这些决策记得最清楚

如果你一直在空着用 /compact,本质上是在让 Claude 替你决定什么值得记住。


两个可以立刻自己验证的小实验

如果你想判断这篇文章讲的是不是“经验之谈”,最好的办法不是相信我,而是自己试一下。

实验一:同一条规则,留在对话里 vs 写进 CLAUDE.md

找一条你平时经常纠正的规则,比如:

不要用 console.log,用 logger.info

先只在对话里说这条规则,继续推进几轮任务,再让 Claude 生成新代码。然后把同一条规则写进 CLAUDE.md,重新开始一个类似任务,再观察它的稳定性。

你大概率会看到一个很明显的差别:
留在对话里的规则更容易回退;写进 CLAUDE.md 的规则更稳定。

这个实验最重要的启发不是“CLAUDE.md 很有用”,而是:规则所在的层级不同,稳定性就不同。

实验二:空着 /compact vs 带保留说明 /compact

找一段讨论过很多方案的长会话。先在类似场景里直接执行:

/compact

再换一次,在压缩时明确写:

/compact 只保留已确认的决策:JWT 方案、Prisma 表结构、错误处理规范。
探索阶段被否掉的方案不保留。

然后比较压缩后的后续表现。你通常会发现,后者更不容易把探索阶段的噪音继续带下去。

这个实验说明的不是“提示词可以调得更好”,而是:/compact 本身就在重新组织历史,所以你不该把它当成无脑归档。


完整视图:五类控制机制

把命令扩展到所有控制机制,共五类:

类型 触发方式 经过 AI 可靠性
CLI 状态命令(/clear/cost 等) 手动输入 确定性
自定义 Slash Command 手动输入 依赖提示词质量
Skills 命令或自然语言触发 工具权限有硬约束
Hooks(PreToolUse 等) 工具执行事件自动触发 确定性
键盘快捷键(Plan Mode 等) 键盘操作 确定性

不经过 AI 的机制(CLI 命令、Hooks、快捷键)是确定性的,适合做强约束;经过 AI 的机制(自定义命令、Skills)有随机性,需要好的提示词设计,但表达能力更强。


这篇文章的边界

这里讲的现象,并不是 Claude Code 独有的怪癖,而是长上下文 AI 协作的一类共性问题。不同模型、不同客户端、不同版本,细节会有差别,但下面这几个事实不会变:

  • 对话历史不是长期稳定记忆
  • 早期探索内容会污染后续判断
  • 留在对话层的规则会衰减
  • 当上下文过长时,注意力一定会分散

所以这篇文章真正想讲的,不是“背命令表”,而是一个更底层的判断:

Claude Code 的核心问题,不是你会不会再写一个更长的 Prompt,而是你有没有把信息放在正确的层级上。

项目背景、长期规则、任务上下文、阶段性探索,这四种东西不应该混在一起管理。Claude Code 之所以有这些 Commands,本质上就是为了让你把它们拆开。


本篇实践任务

任务一: 找你最近一次“感觉 Claude 越来越糊涂”的会话,对照四种劣化模式,判断到底是哪一种在起作用,不要再笼统地归因于“模型变差”。

任务二: 检查你现在的 CLAUDE.md,有没有把“曾经在对话里纠正过不止一次”的规则写进去?把它们补进去,下次对话观察差异。

任务三: 做一次对照实验:一段长任务会话里分别试试“空着 /compact”和“带保留说明 /compact”,比较后续回答质量。

任务四:.claude/commands/ 里创建一个你最常用操作的自定义 command,比如 /pr-desc/standup,感受一下它和直接输提示词的区别。


下篇预告

第 04 篇:CLAUDE.md 完整指南——让 Claude 真正理解你的项目

你已经知道 CLAUDE.md 是"系统注入的长期记忆",优先级高于对话历史,是纠正回退的唯一可靠手段。但写什么进去、怎么写才能真正影响 Claude 的行为而不只是让它"读到",是另一个问题。下一篇专门讲这个。


AI Coding 系列持续更新。上下文劣化有规律,干预就有方法。

AI Mind v0.0.8:从单 Skill 到多 Skill,我如何让第二个 Skill 真正成立

本文对应项目版本:v0.0.8

v0.0.7 里,我已经给 AI Mind 落下了第一个正式 Skill:utility-skill

那一版的重点,是证明一件事:

在 Multi-Tool Runtime 之上,是否真的能再长出一层更稳定的能力模式。

但只有一个 Skill,其实还不够。

因为单 Skill 最多只能证明:

  • 这套结构能跑
  • Runtime 能感知 Skill
  • Tool 可以被 Skill 收口

它还证明不了另一件更关键的事:

当系统开始进入多 Skill 阶段时,这层抽象到底是不是真的成立。

所以到了 v0.0.8,我真正要回答的问题就变成了:

  1. 第二个 Skill 应该是什么
  2. 什么样的 Skill 才值得进入正式版本
  3. 多 Skill Runtime 的边界应该怎么收
  4. 前端又该怎么把这种能力模式切换表达出来

这篇文章想讲的,就是我如何从最初的 writer-skill 设想,最后收敛到了 reader-skill,并让 AI Mind 真正迈出“从单 Skill 到多 Skill”的第一步。

skill-1.gif

为什么多 Skill 是这一版必须面对的问题

如果项目一直只有一个 utility-skill,那 Skill Runtime 很容易停留在一个比较尴尬的状态:

  • 看起来像是做出了一层新抽象
  • 但又很难证明它不是一次性的特殊 case

因为只有一个 Skill 时,你很难回答这些问题:

  • Skill 之间的边界能不能真正拉开
  • Runtime 是否能根据不同 Skill 暴露不同 Tool 子集
  • 自动模式下的路由是否还有意义
  • 前端是否需要为不同 Skill 提供更明确的交互入口

换句话说,单 Skill 更像是在证明“这套机制存在”,而多 Skill 才开始证明“这套机制成立”。

所以 v0.0.8 的重点,不是再多做一个功能,而是:

让第二个 Skill 真正成为一个有独立边界、有独立 Tool 价值的能力模式。

为什么最开始想到的是 writer-skill

一开始我最自然想到的第二个 Skill,其实是 writer-skill

这个方向表面上看很合理:

  • 它和 utility-skill 差异足够大
  • 用户很容易理解“写作模式”
  • 前端做模式切换时,也很容易有感知

所以我最初尝试的方向是:

  • 做一个 writer-skill
  • 再配一个偏结构整理的 Tool
  • 让模型在“改写、总结、整理、生成标题”这类任务上走另一条路径

从想法上说,这条线没有问题。

真正的问题出在落地后。

很快我就发现,写作整理类任务和 utility-skill 最大的不同在于:

它们里有很大一部分,其实本来就是大模型原生就会做的事情。

比如:

  • 润色一段话
  • 把几句话改写得更自然
  • 整理成一段通顺表达
  • 概括几个点

这些任务里,模型往往会直接回答,而不是老老实实触发 Tool。

也就是说,writer-skill 可以命中,但 Tool 的独特价值却不够稳定。

为什么我最后放弃了 writer-skill

最后让我决定止损的,不是某一个 bug,而是一个越来越明确的判断:

第二个正式 Skill,最好补的是模型没有的能力,而不是模型已经比较擅长的能力。

writer-skill 的问题主要有三个。

1. 写作整理很多时候是模型原生能力

写作并不是不能做成 Skill,而是它很难在当前阶段承担“证明多 Skill Runtime 成立”的任务。

因为一旦用户的需求是:

  • 改写
  • 润色
  • 概括
  • 整理成更自然的一段话

模型会天然倾向于自己直接写。

这意味着:

  • Skill 也许命中了
  • 但 Tool 不一定会稳定触发

2. Tool 没形成“非它不可”的能力差

如果一个 Tool 提供的只是:

  • 换个结构
  • 换个格式
  • 帮你整理一下语序

那它很容易被模型直接绕过去。

因为模型会判断:

我自己直接写一段,往往比调用一个结构整理 Tool 更简单。

这和 calculatordatetimeunit-convert 完全不一样。

后者一旦不用 Tool,模型就很容易答错;而前者即使不用 Tool,模型也很可能还能答得不错。

3. 第二个 Skill 不应该只是“多一种说话风格”

这是这轮最重要的取舍。

我最后越来越明确地意识到:

多 Skill 的关键,不是“多几个模式名字”,也不是“多几段 Prompt”。

真正值得留下来的 Skill,应该满足至少一条:

  • 它组织了一组边界清晰的 Tool
  • 它补的是模型原本拿不到的能力
  • 它能明显改变 Runtime 的可用能力范围

writer-skill 在当前阶段没有足够强地满足这些条件。

所以我最后放弃它,并不是因为写作不重要,而是因为它不适合当前作为“第二个正式 Skill”。

为什么第二个 Skill 最终变成了 reader-skill

我最后把第二个 Skill 改成了 reader-skill

因为这时我更想验证的是:

Skill Runtime 是否能承载一类模型本身完全拿不到的信息能力。

reader-skill 对应的正是这类场景:

  • 实时天气
  • 本地文本文件

它们有一个共同点:

没有 Tool,就没有能力来源。

这和写作场景最大的区别在于:

  • 没有天气 Tool,模型就拿不到实时天气
  • 没有本地文件读取 Tool,模型就看不到你的项目文件

这时候 Tool 不再是“可选增强”,而是“能力成立的前提”。

于是 reader-skill 的价值就变得非常明确:

  • 它不是在给模型增加一种风格
  • 而是在给模型接入一类新的上下文来源

这就让第二个 Skill 终于拥有了足够清晰的独立边界。

这版 reader-skill 是怎么收边界的

reader-skill 这一版我只落了两个 Tool,而且每个 Tool 都故意收得很小。

1. city-weather

用途非常单一:

  • 查询指定城市的实时天气

它只收一个参数:

  • city

数据源我也没有做得很重,而是直接用了轻量的 wttr.in

这背后的考虑很简单:

  • 这版的重点不是做一个完整天气系统
  • 而是验证“实时信息如何进入 Skill Runtime”

也就是说,city-weather 的价值不在于“做得多强”,而在于它非常直接地证明了:

没有外部 Tool,模型就是拿不到这部分实时信息。

2. local-text-read

local-text-read 同样只做一件事:

  • 读取项目根目录下的直接文本文件

它也只收一个参数:

  • filename

而且我给它加了很强的边界限制:

  • 只允许根目录直接文件
  • 不允许子目录
  • 不允许绝对路径
  • 不允许 ../
  • 只允许文本类文件

这也是我这一版很看重的一点:

Tool 的价值不只是“能做什么”,还包括“它不会越界做什么”。

如果第二个 Skill 要证明它是一种正式能力模式,那它不仅要有能力来源,也要有稳定边界。

多 Skill Runtime 这一版真正收敛了什么

v0.0.8,项目里的 Skill 边界终于开始变清楚了。

现在可以比较明确地把它们分成两类:

utility-skill

负责确定性实用任务:

  • 计算
  • 时间日期
  • 单位换算
  • 文本转换

对应 Tool:

  • calculator
  • datetime
  • unit-convert
  • text-transform

reader-skill

负责外部上下文获取:

  • 实时天气
  • 本地文件读取

对应 Tool:

  • city-weather
  • local-text-read

这时候 Skill 才真正不再只是“一个标签”,而是:

  • 当前属于哪一种能力模式
  • 当前允许模型使用哪些 Tool
  • 当前回答主要建立在哪一类能力来源上

多 Skill 链路图

flowchart LR
    A["用户请求"] --> B["/api/chat"]
    B --> C{"是否显式传入 skill"}
    C -- "是" --> D["直接命中对应 Skill"]
    C -- "否" --> E["轻量规则路由"]
    E --> F{"命中 utility / reader ?"}
    F -- "utility" --> G["utility-skill"]
    F -- "reader" --> H["reader-skill"]
    F -- "未命中" --> I["普通聊天链路"]
    G --> J["allowedTools 过滤 ToolRegistry"]
    H --> J
    J --> K["模型在当前 Tool 子集里决定是否调用 Tool"]
    K --> L["Runtime 执行 Tool"]
    L --> M["流式返回 reasoning / tool / text"]

这一版里我刻意没有做的,是:

  • 模型自主 Skill 路由
  • 多 Skill 编排
  • 更复杂的 Agent 化链路

因为我想先证明的不是“系统越来越聪明”,而是:

多 Skill Runtime 在结构上已经开始稳定成立。

为什么前端也要一起进入正式组件基线

这版还有一个我认为非常值得一起写进去的变化:

前端开始正式进入 shadcn/ui 基线阶段。

原因其实也很现实。

当输入区开始同时出现:

  • 模型选择器
  • Skill 模式切换
  • 深度思考开关
  • 推理过程面板
  • Tool 卡片

如果继续完全靠手写样式往前堆,界面会越来越像几套东西拼在一起。

所以这一版我顺手做了前端统一收口:

  • 正式接入 shadcn/ui
  • 使用 Radix
  • 图标统一为 lucide-react
  • 主题走 cssVariables
  • 当前基线切到 radix-vega

这一轮已经统一下来的区域包括:

  • 输入区控制条
  • 顶部错误条
  • 推理过程面板
  • Tool 卡片
  • 空状态

而且我还补了几项比较细的交互收口:

  • 输入框上下边距收紧
  • 推理面板上下边距收紧
  • Tool 状态色区分:
    • 完成:绿
    • 执行中:蓝
    • 失败:红
  • 实用读取 模式下的提示文案分开

这一点对我来说很重要,因为它说明:

多 Skill Runtime 的成立,不只是后端 Runtime 的问题,也是前端表达能力的一部分。

skill-2.gif

这版最重要的工程结论

如果要我用几句话总结 v0.0.8,我最想留下的是这三点:

1. 不是所有 Skill 都值得进入正式版本

有些 Skill 看起来方向对,但它不一定适合当前版本的验证目标。

writer-skill 就属于这种情况:

  • 它不是完全没价值
  • 但它不适合当前承担“证明第二个 Skill 成立”的任务

2. 第二个 Skill 最好补的是模型缺失的能力

如果 Tool 补的是模型原本就会做的事情,那它就很容易被绕过。

但如果 Tool 补的是模型完全拿不到的上下文,那 Skill 的价值会立刻清晰很多。

这也是为什么 reader-skillwriter-skill 更适合当前阶段。

3. 多 Skill 的成立,不只是 Runtime 的事,也是 UI 的事

一旦系统开始真正区分:

  • 自动 / 实用 / 读取
  • reasoning / tool / text
  • 不同 Tool 状态

那前端也必须同步给出更统一、更稳定的表达方式。

这也是为什么这版里,我没有把 UI 统一看成“顺手做的样式活”。

它其实也是版本收敛的一部分。

这一版之后,我更清楚了一件事

如果说 v0.0.7 证明的是:

Tool Runtime 之上可以长出第一层 Skill Runtime。

那么 v0.0.8 证明的就是:

多 Skill 不是多几个不同名字的 Prompt,而是第二个 Skill 是否真的打开了一块新的能力边界。

对现在的 AI Mind 来说,这块边界已经开始变得清楚:

  • utility-skill:确定性实用任务
  • reader-skill:外部上下文获取

这也让整个 Runtime Skeleton 比之前更像一个会继续长大的系统,而不是一组不断堆叠的局部功能。

后面会往哪走

如果继续沿这条线往后走,我更关心的是:

  • reader-skill 的稳定性继续收口
  • 网页读取 / MCP 能力怎么接入
  • 更高层的 Agent Runtime 什么时候开始真正有必要

但至少在 v0.0.8 这个点上,我已经比较确认:

第二个 Skill 终于不是一个“看起来像 Skill 的名字”,而是一块真正成立的能力模式。

最后

这个项目还会继续沿着:

  • reader-skill 稳定性收口
  • 网页读取 / MCP
  • Agent Runtime

这些方向继续往前走。

如果这篇文章对你有帮助,欢迎到 GitHub 看看项目,也欢迎顺手点个 Star。

仓库地址: github.com/HWYD/ai-min…

Proxy 与 Namespace:终结环境与鉴权的噩梦

Proxy 与 Namespace:终结环境与鉴权的噩梦

本章基于基础事实来讲述NameSpace的重要性

BFF 如同统一的"海关大楼"图注:所有前端请求在这里统一安检、分流,走向正确的后端服务。

开场故事:被"环境标"与 Cookie 折磨的前端

在没有统一 BFF 的时代,一个前端工程师的日常,常常伴随着一些令人抓狂的场景:

"小王,我本地环境起不来,访问你的接口报跨域了,你那能开一下 CORS 吗?" "李工,麻烦问下,我要联调用户中心的 PPE 环境,请求头里要加哪个环境标来着?是 X-TT-ENV 还是 X-TT-PPE?" "奇怪,我明明在测试环境,为什么创建的订单会出现在线上数据库里?!"

这些问题听起来琐碎,却像慢性毒药一样,日复一日地消耗着前端团队的精力和耐心。

问题摊牌:环境与鉴权的无底洞

这些混乱现象的背后,是几个长期困扰前端的根源性问题:

  1. 环境标的迷宫:后端微服务通常有多套环境:本地、测试、预发布(PPE)、灰度、生产。为了在本地开发时能联调到正确的后端服务,前端不得不在代码里写死后端的 IP,或是在请求工具里手动塞入各种环境标。一旦不小心把带有测试环境标的代码发布到线上,就可能酿成严重的生产事故。
  2. 跨域与鉴权的无底洞:前端页面部署在 a.company.com,却要调用 user.company.comorder.company.com 的接口。为了解决跨域,前端不得不与各种 Access-Control-Allow-Origin 配置斗智斗勇。更糟糕的是鉴权,每个前端项目都得复制粘贴一套逻辑去解析 Token、判断登录态、处理会话过期,并小心翼翼地将凭证附带在每一个发往不同域的微服务请求上。
  3. "邻居的噪音":公司内部往往有多个业务线(如 C 端商城、B 端中台、内部运营系统),它们可能共享同一套底层微服务。如果 B 端系统在测试时修改了某个全局配置,正在开发的 C 端系统就可能莫名其妙地报错。团队之间缺乏有效的逻辑隔离,互相干扰成为常态。

解法白话:构建带有多租户隔离的"智能防线"

为了彻底终结这场噩梦,笔者在 BFF 中引入了两个最基础也最重要的模块:Proxy (统一代理)Namespace (命名空间)

笔者的设计思路非常明确:让前端变得绝对"无脑"

前端工程师只需要向同域名的 BFF 发起最普通的 HTTP 请求,不关心目标服务的真实地址,不关心当前是什么环境,也不用操心如何携带 Token。剩下的所有脏活累活——鉴权、环境路由映射、协议转换、凭证注入——全部由 BFF 在后台悄无声息地完成。

多租户隔离,保障各业务线互不干扰图注:每个 Namespace 如同独立的办公区,拥有自己的门禁和访客规则。

同时,笔者引入了"命名空间 (Namespace)"的概念,实现了多租户隔离,并将不同的业务线(或客户)划分到独立的 Namespace 中。每个 Namespace 拥有自己独立的认证模式 (AuthMode)、独立的路由元数据 (RouteMeta) 和独立的微服务版本映射。这就像为每个团队分配了专属的、隔音的办公室,互不打扰。

技术展开:拦截、查表、注入与透传

在代码实现上,这套机制如同一条精密的流水线,优雅地处理着每一笔前端请求:

  1. 路由与元数据映射 (RouteMeta): 前端发起的请求路径通常被设计为一种约定格式,例如 /proxy/:namespace/:service/:method。当这样的请求到达 BFF 的 ProxyController 时,BFF 首先会根据 :namespace:service 去数据库(或 Redis 缓存)中查找 RouteMeta。这一步确保了系统只代理那些真正在 BFF 注册过的、合法的接口,有效防止了恶意扫描。同时,BFF 会从元数据中获取目标微服务真实的 BaseUrl 和内部 RPC 路径。

  2. 鉴权清洗与注入 (AuthGuard): 请求在进入代理转发逻辑前,会先经过一道严格的鉴权守卫 AuthGuard。BFF 会提取前端请求中携带的 Token,不仅验证其签名和有效期,还会查询 Redis 中的用户会话(Session)以确认其真实性。 关键动作:BFF 在这一步会将外部传入的、可能不安全的凭证(如 Cookie、原始 Token)彻底"清洗"掉。然后,在转发给后端微服务的请求头中,极其确定地注入一个 X-USER-ID。这样一来,下游的微服务彻底解放,它们不再需要关心"如何解密 Token",只要读取请求头里的 X-USER-ID,就可以百分之百地信任这个请求的身份。

  3. 环境标的智能注入: 对于多环境路由,BFF 实现了完全的自动化。如果运维或开发同学在 BFF 的管理后台将当前 Namespace 的环境切换到了 PPE,那么 BFF 在代理转发时,会自动在请求头中打上 X-TT-ENV=ppe 的标签。前端代码一行环境变量都不用修改,就能在各套测试环境间无缝漫游,彻底告别手动维护环境配置的痛苦。

  4. 微服务版本的灵活切换: 不同于传统方案中微服务版本被硬编码在前端配置里,BFF 支持在管理后aps.bytdiance.com台上动态配置每个 Namespace 下各微服务的版本。这意味着同一个前端应用,在不同的 Namespace 下,可以自动调度到完全不同的微服务实例。A 客户使用的是 v2.3.1 版本的用户服务,B 客户可能已经在体验 v2.4.0 了,而这种版本差异对前端完全透明。

  5. 跨域的彻底消解: 由于所有的前端请求都先打到同域的 BFF,再由 BFF 转发到各个微服务,浏览器根本感知不到跨域的存在。CORS 配置从此不再是前端的噩梦,后端微服务也无需各自配置 CORS 头,BFF 统一处理。

一句话总结

Proxy 模块是 BFF 的"万能翻译官",把前端发送的约定式请求,精准地翻译成后端微服务期望的 RPC 调用。

Namespace 模块是 BFF 的"空间隔离官",确保不同租户、不同业务线的配置与权限老死不相往来。

有了这两道防线,前端终于可以安心写业务,后端也能专注写服务,运维更是从此告别"环境噩梦"。

接下来笔者会开始着重讲解NmaeSpace的细节逻辑

日志诊断 Skill:用 AI + MCP 一键解决BUG|得物技术

一、概述

做后端开发,调 BUG 有一个让人头疼的固定流程:打开日志平台,输入 traceId 或关键词,搜日志;从几十上百条日志里,找到关键的那几条;把日志里的类名、方法名复制出来,去 IDE 里找对应代码;结合代码逻辑,判断哪里出了问题;如果一次找不准,回去再搜日志,再翻代码……

这个过程相对固定,但非常耗时间。每次 BUG 定位,光在日志平台和 IDE 之间来回切换,就能消耗掉大半的时间。

最开始在去年 Q3 想到这个问题的时候,脑子里浮现的第一个方案是:用 Cursor + MCP,把日志平台接进来,再挂一个代码知识库,让 AI 帮我查日志。但这个方案有缺陷 —— 日志查询是「动态的」,它依赖环境、应用、时间范围,没办法静态预置。此外,这样处理没有办法做到比较丝滑地读代码、改代码。

后来开始用 Claude Code,接触到了 Skill 的概念:可以在项目里定义一套自定义命令,描述 AI 应该怎么执行这个命令的每个步骤,于是整个思路变得清晰了。

日志平台有 MCP,Claude Code 有 Skill,两者结合,就能让 AI 自动完成「查日志 → 找关键信息 → 扫描代码 → 定位问题」这整个闭环。然后在 PM 的帮助下,才有了 /log-diagnosis 这个 Skill。

二、日志平台 MCP 是什么

MCP 原理

日志平台推出了基于 MCP(Model Context Protocol)协议的日志查询服务,让 Claude 可以直接调用日志平台的能力,无需人工在日志平台上手动查询。

MCP 本质上是一种标准化的「工具调用协议」,Claude Code 通过 SSE(Server-Sent Events)长连接与 MCP Server 通信,实时获取日志数据。

MCP 环境对照

核心 MCP 工具

鉴权流程

secretKey(日志平台后管申请)
    ↓ acquireTokenTool
accessToken(1小时有效,最多同时存在5个)
    ↓ 携带 accessToken
logsQuery / logSqlQuery / countLogTool ...

secretKey 申请地址:进入日志管理后台 → 日志权限 → 我的应用 → 生成密钥。

三、/log-diagnosis Skill 是什么

Skill 工作原理

log-diagnosis 是一个运行在 Claude Code 里的自定义诊断命令。Claude Code 支持通过 .claude/skills/ 目录定义自定义技能(Skill),以 Markdown 文件描述行为规范,Claude 在收到对应命令时会自动加载并执行。你只需要把 traceId 或告警信息告诉它,剩下的全部交给 AI。完整执行链路如下:

用户输入 /log-diagnosis {环境} {代码分支} {诉求}
    ↓
Claude 加载 .claude/skills/log-diagnosis/SKILL.md
    ↓
读取 .diagnosis/config.json 获取当前环境配置
    ↓
检查 accessToken 是否过期,过期则自动刷新
    ↓
从 traceId 计算日志时间范围(取第9-16位16进制时间戳)
    ↓
调用日志平台 MCP 分页拉取全量日志(最多20页,不遗漏)
    ↓
切换到指定代码分支,结合日志关键词检索代码
    ↓
综合分析:上游日志 + 当前服务日志 + 代码逻辑 → 根因
    ↓
生成诊断报告(飞书文档 or 本地 Markdown)
    ↓
恢复原始代码分支

两种诊断入口

核心能力

  • Token 自动管理:accessToken 过期自动刷新,无需手动维护;
  • 分页全量拉取:自动分页拉完所有日志,禁止只查第一页就下结论(最多 20 页);
  • 跨服务分析:自动识别上下游服务,拉取关联服务日志交叉验证;
  • 代码联动:日志里出现的类名/方法名,直接在代码里精确定位。

queryString 语法规则

# 格式
{field} {操作符} "{值}" {连接符} {field} {操作符} "{值}"
# 操作符
=  : 精确匹配
≈  : 模糊匹配(like)
# 连接符
AND / OR / NOT
# 示例
trace_id"a1b2c3d4e5f6789012345678abcdef01"
trace_id"xxx" AND log_level = "ERROR"
endpoint ≈ "/api/your-endpoint" AND log_level"ERROR"
message ≈ "timeout"

注意:时间范围只通过 start/end 参数控制,不要写在 queryString 中。

四、安装与配置

安装日志平台 MCP

Claude Code

在 Claude Code 命令行中执行,按需安装对应环境:

# 测试环境
claude mcp add --transport sse dw-log-mcp-t1 https://{your-t1-aigw-domain}/api/v1/mcp/log-mcp/sse
# 预发环境
claude mcp add --transport sse dw-log-mcp-pre https://{your-pre-aigw-domain}/api/v1/mcp/log-mcp/sse
# 生产环境
claude mcp add --transport sse dw-log-mcp-prd https://{your-prd-aigw-domain}/api/v1/mcp/log-mcp/sse

安装后重启 Claude Code,执行 /mcp 确认连接状态正常。

Cursor

  1. 打开 Cursor Setting;

  2. 点击 Tools & MCP,添加 MCP Server;

  3. 添加 URL,MCP Server 名称任意。

建议按需安装 MCP Server,避免额外消耗 token,示例配置:

{
  "mcpServers": {
    "dw-log-mcp-t1": {
      "url": "https://{your-t1-aigw-domain}/api/v1/mcp/log-mcp/sse"
    },
    "dw-log-mcp-pre": {
      "url": "https://{your-pre-aigw-domain}/api/v1/mcp/log-mcp/sse"
    },
    "dw-log-mcp-prd": {
      "url": "https://{your-prd-aigw-domain}/api/v1/mcp/log-mcp/sse"
    },
    "dw-log-mcp-oversea-prd": {
      "url": "https://{your-oversea-aigw-domain}/api/v1/mcp/log-mcp/sse"
    }
  }
}

4. 返回设置,就可以看到已经连接上。

安装 /log-diagnosis Skill

将 log-diagnosis 目录放到项目的对应目录下:

Claude Code

your-project/
└── .claude/
    └── skills/
        └── log-diagnosis/
            ├── SKILL.md        # 技能行为规范(核心)
            ├── README.md       # 使用说明
            └── reference.md   # 附录:时间脚本、queryString 示例等

Cursor

your-project/
└── .cursor/
    └── skills/
        └── log-diagnosis/
            ├── SKILL.md        # 技能行为规范(核心)
            ├── README.md       # 使用说明
            └── reference.md   # 附录:时间脚本、queryString 示例等

配置 .diagnosis/config.json

首次运行会自动引导创建 (直接调用 /log-diagnosis,Skill 会一步步指示你给出 secret key),也可手动在项目根目录创建 .diagnosis/config.json:

your-project/
└── .cursor/
    └── skills/
        └── log-diagnosis/
            ├── SKILL.md        # 技能行为规范(核心)
            ├── README.md       # 使用说明
            └── reference.md   # 附录:时间脚本、queryString 示例等

字段说明:

secretKey:唯一需要人工填写的字段,在日志平台后管申请;

accessToken:首次使用时由 AI 自动调用 acquireTokenTool 获取,过期自动刷新;

accessTokenExpireAt:从 acquireTokenTool 返回值自动填充;

fields:调用 logFields 工具自动获取。

五、使用方式

命令格式:

/log-diagnosis {环境} {代码分支(可选)} {诉求描述}

参数说明:

  • {环境}:T1 / PRE / PRD(按实际环境标识填写);
  • {代码分支}:可选,留空则使用当前分支;
  • {诉求描述}:包含 traceId 或告警信息的问题描述,用自然语言书写即可。

示例:

# 用 traceId 定位接口异常
/log-diagnosis T1 feature/your-branch trace_id: "your-trace" 为什么最终没有返回数据
# 用告警信息分析错误原因
/log-diagnosis PRD master 告警详情:【接口:YourService/yourMethod】【业务码:10002000】【业务码消息:系统异常,请稍后重试】帮我分析问题可能性

一行命令,AI 全程接管,几分钟内给出根因分析。

六、实战案例:一个隐蔽的 SQL BUG

背景

某搜索接口在测试环境反馈没有返回数据。拿到 traceId,直接执行:

/log-diagnosis T1 feature/your-branch trace_id: "your-trace" 为什么最终没有返回数据

← 就这一句话,接下来全部交给 AI。

AI 自动拉取日志

Skill 触发后,AI 自动完成:

  • 从 traceId 推算出日志时间范围(2026-02-27 全天);
  • 检查 accessToken 已过期,自动刷新;
  • 调用日志平台 MCP,分 2 页拉取完整日志,共 73 条。

请求入参(从日志自动提取):

{
  "assembleByOrg": true,
  "channelType": "MANUAL",
  "orderNo": "your-order-no",
  "status": 1,
  "ticketNo": "your-ticket-no"
}

AI 还原完整调用链路

AI 自动识别出关键节点:resultList is empty,SQL 查询返回了空结果。问题在 DB 层,而不在业务逻辑层。

AI 提取组装后的查询 DTO

从日志中提取到 toSearchDTO 组装结果:

{
  "channelType": "MANUAL",
  "customerTag": 1,
  "deliveryMode": "某配送方式",
  "orderStatus": "8010",
  "orderType": "0",
  "productCategoryIds": [29],
  "status": 1,
  "ticketSource": 67,
  "ticketTypeId": 5802
}

AI 从日志中提取实际执行的 SQL 发现根因

ORM 框架在日志中打印了实际执行的 SQL,AI 直接读取并分析:

SELECT a.id, a.pid, a.name, a.mode, a.status, a.org_id, a.org_ids,
       a.ticket_group_id, a.tenant_id, a.is_del, a.channel_types
FROM your_type_table a
LEFT JOIN your_relation_table b
    ON b.tenant_id = 1 AND a.id = b.type_id AND b.type = 3 AND b.is_del = 0
WHERE a.tenant_id = 1 AND a.mode = 2 AND a.is_del = 0
  AND a.status = 1
  AND (a.channel_types IS NULL OR a.channel_types = '' OR FIND_IN_SET('MANUAL', a.channel_types) > 0)
  AND (b.root_id is null or b.root_id in (29))
  AND (a.order_types IS NULL OR a.order_types = '' OR FIND_IN_SET('0', a.order_types) > 0)
  AND (a.order_statuses IS NULL OR a.order_statuses = '' OR FIND_IN_SET('8010', a.order_statuses) > 0)
  AND (a.delivery_modes IS NULL OR a.delivery_modes = '' OR FIND_IN_SET('某配送方式', a.delivery_modes) > 0)
  AND (a.ticket_sources IS NULL OR a.ticket_sources = '' OR FIND_IN_SET(67, a.ticket_sources) > 0)
  AND (a.customer_tag IS NULL OR a.customer_tag = 1)   ← BUG 在此

AI 发现:其他字段都处理了 IS NULL 和 = ''(空字符串代表 “不限制”)两种情况,唯独 customer_tag 只判断了 IS NULL,遗漏了空字符串 '' 的情况。

SQL 语义对比:

-- 其他字段(正确):IS NULL 和 '' 都处理了
AND (a.order_types IS NULL OR a.order_types'' OR FIND_IN_SET('0', a.order_types) > 0)
AND (a.delivery_modes IS NULL OR a.delivery_modes'' OR FIND_IN_SET('某配送方式', a.delivery_modes) > 0)
AND (a.ticket_sources IS NULL OR a.ticket_sources'' OR FIND_IN_SET(67, a.ticket_sources) > 0)
-- customer_tag(遗漏了 = '' 的判断)← BUG
AND (a.customer_tag IS NULL OR a.customer_tag1)

DB 中现有的数据,customer_tag 字段都存的是空字符串(未配置),按业务语义本应匹配所有请求,却因为这个遗漏被全部过滤掉了。

AI 定位代码,给出修复方案

AI 在代码中直接找到对应的 MyBatis Mapper XML:

<!-- 问题代码 -->
<if test="customerTag != null">
    and (a.customer_tag IS NULL OR a.customer_tag = #{customerTag})
</if>
<!-- 修复后 -->
<if test="customerTag != null">
    and (a.customer_tag IS NULL OR a.customer_tag = '' OR a.customer_tag = #{customerTag})
</if>

效率对比

这个 BUG 的隐蔽性在于:SQL 语法正确,逻辑上也「看起来」没问题——只有对比了其他字段的写法,才能发现 customer_tag 独自遗漏了空字符串的处理。这类细节差异,人工排查很容易忽略,AI 反而很擅长。

七、诊断效率关键点

  • 有 traceId 时优先用 traceId 拉日志,可精准获取单次请求的完整链路,比关键词搜索精确得多;
  • 关注关键日志节点:toSearchDTO finished / search begins / resultList is empty / search finished 等,快速判断数据在哪一层丢失;
  • SQL 打印日志(ORM 框架输出)是黄金线索,直接反映最终执行的查询条件,AI 能从中发现肉眼难以察觉的差异;
  • 分页必须拉完:日志平台一次只返回部分数据,AI 会严格执行分页直到取完,确保不遗漏关键日志。

八、总结

核心思路:用「协议 + 规范」让 AI 接管固定流程:

这篇文章的本质,是一次对重复性工程劳动的自动化尝试。调 BUG 的过程——查日志、提取关键信息、找代码、分析原因——逻辑固定,步骤繁琐,但并不需要太多创造性思维。这类工作恰好是 AI 最擅长接管的。

实现这个闭环,靠的是两个关键组合:

  • MCP:让 AI 能够调用外部系统(日志平台),突破了「AI 只能处理静态上下文」的限制,实现了对动态数据的实时获取。
  • Skill:给 AI 一份行为规范,告诉它每一步该怎么做、先做什么后做什么、遇到什么情况怎么处理,把「一次性对话」变成「可复用的工程化能力」。

两者缺一不可。只有 MCP,AI 能查日志但不知道怎么系统地分析;只有 Skill,AI 有流程但没有数据来源。组合起来,才形成了真正可落地的闭环。

值得借鉴的地方:

识别「固定流程」是自动化的起点:不是所有工作都适合 AI 接管,但凡是「步骤固定、信息来源明确、输出格式可预期」的工作,都值得尝试用 Skill + MCP 的方式来自动化。排查 BUG 是一个典型,类似的还有:代码审查、性能分析报告生成、告警巡检等。

Skill 的本质是「给 AI 写操作手册」:Skill 文件不是在「训练模型」,而是在给 AI 一份清晰的 SOP。写得越细、约束越明确(比如「禁止只查第一页就下结论」「必须分页拉完所有数据」),AI 的执行质量越稳定。这和写给人看的文档本质上是一回事。

AI 擅长发现「横向对比」类的 BUG:本文的案例揭示了一个有意思的规律:AI 在处理「同类字段逻辑不一致」这类问题时,表现往往比人工更好。原因在于 AI 没有「先入为主」的经验偏见,不会因为「这段代码看起来没问题」就跳过,它会对所有字段做同等的审查。

最后说一句:AI 时代,工程师的核心竞争力不只是「能写代码」,更是「能把自己的经验和流程转化成可复用的 AI 能力」。/log-diagnosis 是一次小小的尝试,但背后的思路,值得在更多场景里延伸。

往期回顾

1.Redis 自动化运维最佳实践|得物技术

2.Claude在得物App数仓的深度集成与效能演进

3.Claude Code + OpenSpec 正在加速 AICoding 落地:从模型博弈到工程化的范式转移|得物技术

4.大禹平台:流批一体离线Dump平台的设计与应用|得物技术

5.基于 Cursor Agent 的流水线 AI CR 实践|得物技术

文 /阿程

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

iOS 多技术栈混淆实现,跨平台 App 混淆拆解与组合

当项目从单一 iOS 原生扩展到 Flutter、React Native 或 Unity 时,混淆这件事会变得复杂。原因不在于工具少,而是每一层代码完全不同

  • Swift / Objective-C → Mach-O 符号
  • Flutter → Dart AOT + assets
  • React Native → JS bundle
  • Unity → DLL + 资源

如果只用一种 iOS 混淆工具,通常只能覆盖其中一部分。


不同技术栈暴露的信息完全不一样

拿一个混合项目举例(Flutter + 原生 + H5),解包 IPA 后可以看到:

AppBinary          // 原生代码
flutter_assets/    // Dart + 资源
main.jsbundle      // JS 逻辑
assets/            // 图片与配置

每一层的“暴露方式”不同:

技术 可被读取的内容
Swift / OC 类名、方法名、参数
Flutter Dart 符号(部分)、资源路径
React Native JS 逻辑
Unity DLL + AssetBundle

这意味着混淆必须分层处理。


原生层:符号混淆(iOS 混淆工具核心能力)

先看最传统的一层:Swift / Objective-C。

检查方式:

strings AppBinary | grep Controller

如果看到:

HomeViewController
PaymentManager

说明符号未处理。


处理方式

使用 Ipa Guard 这类 IPA 级别的 iOS 混淆工具:

  • 导入 IPA
  • 进入代码模块
  • 勾选类 / 方法 / 参数

执行后:

PaymentManager → a82kd3

这一步直接改变 Mach-O 符号,是跨平台项目中最“统一”的一层处理。


Flutter 层:Dart 混淆 + IPA 补充

Flutter 提供内置混淆:

flutter build ios --obfuscate --split-debug-info=./symbols

执行后:

  • Dart 符号被替换
  • 生成符号映射

但 IPA 解包后仍然可以看到:

assets/images/banner.png
config/app.json

补充处理

使用 Ipa Guard 的资源模块:

banner.png → x92kd.png
app.json → a83ks.json

这样 Dart 层 + 资源层同时处理。


React Native:JS 混淆 + 文件重命名

React Native 的关键在 JS bundle:

main.jsbundle

直接打开可以读。


处理步骤

1)压缩 JS:

terser main.js -o main.min.js

2)替换 bundle

3)用 Ipa Guard 修改文件名称:

main.jsbundle → k39sd.bundle

这样:

  • 内容不可读
  • 路径无语义

Unity:资源与 DLL 的组合处理

Unity 项目解包后:

Data/Managed/Assembly-CSharp.dll
Data/Resources/

DLL 可以被反编译,资源路径也能推断逻辑。


处理方式

  • 使用 Unity 构建参数减少符号
  • 在 IPA 层用 Ipa Guard 处理资源名称
  • 修改资源 MD5

例如:

level1.assetbundle → a82kd.bundle

统一处理:资源指纹与结构差异

跨平台项目中,资源重复是一个常见问题。

例如多个 App 使用同一套 UI:

banner.png
icon.png

即使改名,内容仍然一致。

处理方式

在 Ipa Guard 中开启 MD5 修改:

md5 banner.png

处理前后不同。

这一步可以打散资源特征。


七、调试信息清理

检查:

strings AppBinary | grep NSLog

或:

strings AppBinary | grep Flutter

如果存在调试信息,可以统一清理。Ipa Guard 支持删除调试符号和部分日志字符串。


签名工具

无论哪个技术栈,只要修改 IPA,就必须重新签名。

可以使用:

kxsign sign app.ipa \
-c cert.p12 \
-p password \
-m dev.mobileprovision \
-z test.ipa \
-i

深入理解 Node.js:生态体系与事件循环机制详解

深入理解 Node.js:生态体系与事件循环机制详解

Node.js 的诞生彻底打破了 JavaScript 仅能运行在浏览器端的边界,依托 Chrome V8 引擎,它将 JavaScript 带入服务器端开发领域,凭借轻量、高效、生态丰富的特性,成为大前端全栈开发(尤其是 BFF 层)的主流技术选型。本文将从 Node.js 核心特性、生态体系出发,深入解析其核心的事件循环机制,结合实操代码让读者理解其高并发能力的底层逻辑。

一、Node.js 核心特性与生态体系

1. 核心特性:异步无阻塞与单线程高并发

不同于 Java/Go 等多线程语言,Node.js 采用单线程 + 异步无阻塞 I/O 架构,这是其核心优势所在:

  • 单线程的优势:避免了多线程上下文切换的开销,代码逻辑更简单,开发和调试成本更低;
  • 异步无阻塞:对于文件 I/O、网络请求、数据库操作等耗时任务,Node.js 不会阻塞线程等待结果,而是将任务放入事件循环(Event Loop),立刻切换处理新的请求。少量线程即可支撑成千上万的并发连接,服务器资源开销仅为 Java 的一半,这也是 Node.js 高并发能力的核心来源。

2. 核心模块:夯实底层能力

Node.js 内置了丰富的核心模块,覆盖开发核心场景,也是开发者必须掌握的基础:

  • 文件模块(fs) :支持文件的同步 / 异步读写,以及流式处理。例如readFile/writeFile可通过promisify转换为 Promise 风格(避免回调地狱),而readFileSync则是阻塞式读取;createReadStream结合pipe可实现大文件的流式输出,避免一次性加载大文件占用过多内存;
  • 路径模块(path) :处理不同操作系统的路径兼容问题,简化路径拼接、解析等操作;
  • HTTP 模块:原生支持 HTTP 服务搭建,是后续框架的底层基础。

3. 框架生态:从轻量到企业级

Node.js 的框架生态满足不同层级的开发需求:

  • 轻量框架:Express、Koa 是前端开发者入门后端的首选,轻量、灵活,适合快速搭建接口、Mock 服务或 BFF 层应用;
  • 企业级框架:NestJS 基于 TypeScript 开发,天然支持模块化、依赖注入,贴合后端工程化理念,适合大型项目、AI 网关等企业级场景开发。

4. 应用场景与周边生态

Node.js 的生态覆盖全栈开发核心场景:

  • 核心场景:接口转发、实时通信(如 WebSocket)、AI 网关、管理后台开发;
  • BFF 层(Backend For Frontend) :前端开发者可基于 Node.js 封装 Go/Java 后端接口,适配前端业务需求,提升前后端协作效率;
  • 数据库与 ORM:主流适配 MySQL、PostgreSQL,结合 Prisma 等 ORM 工具,简化数据库操作,提升开发效率;
  • AI 开发生态:基于 LangChain 可封装 LLM 调用、构建工具调用(Tool),实现 Agent 开发流程;结合 RAG 技术(文档切分、向量化、向量数据库存储、检索增强),可快速搭建知识库问答系统。

二、Node.js 事件循环机制深度解析

事件循环(Event Loop)是 Node.js 实现异步编程的核心机制,它决定了异步任务的执行顺序。虽然 Node.js 和浏览器的事件循环本质都是 “事件驱动的异步模型”,但因运行环境(服务器 vs 浏览器)不同,实现细节差异显著。

1. 事件循环的核心逻辑

Node.js 的事件循环是一个 “无限循环”,其核心是:同步代码执行完毕后,按阶段依次处理异步任务,每个阶段都有专属的任务队列,且每个阶段执行完后会清空对应微任务队列

2. Node.js 事件循环的核心阶段

Node.js 的事件循环分为多个核心阶段,按执行顺序依次为:

  • timers 阶段:执行setTimeoutsetInterval调度的回调函数,注意setTimeout(fn, 0)并非立即执行,而是等待 timers 阶段触发;
  • poll 阶段:处理 I/O 异步任务(文件、网络、数据库等),是事件循环中最核心的阶段。若 poll 队列有任务,则依次执行;若队列空,则会等待新的 I/O 任务进入,或跳转到 check 阶段;
  • check 阶段:执行setImmediate调度的回调函数,在 poll 阶段空闲后 “强制触发”;
  • 其他阶段(如 idle、prepare、close callbacks):主要为内部逻辑服务,开发者无需重点关注。

3. 微任务队列:优先级高于宏任务

Node.js 的微任务队列包含两类,优先级从高到低为:

  • process.nextTick:独立于事件循环阶段,优先级最高,同步代码执行完后立即执行;
  • Promise 微任务(如Promise.resolve().then()):优先级低于process.nextTick,但高于所有宏任务(timers、poll、check)。

4. 浏览器 vs Node.js 事件循环

  • 浏览器事件循环:核心是 “宏任务→清空微任务→渲染→下一轮宏任务”,宏任务包含 script、setTimeout,微任务仅关注 Promise;
  • Node.js 事件循环:核心是 “多阶段调度”,每个阶段执行完后清空微任务队列,微任务包含process.nextTick和 Promise,且阶段划分更细(适配服务器的文件、网络 I/O 场景)。

5. 代码实例:拆解事件循环执行顺序

结合以下代码,我们逐行分析执行流程,理解事件循环的执行逻辑:

const fs = require('fs')

console.log('start')
// timers 阶段
setTimeout(() => {
  console.log('timeout')
}, 0)
// check 阶段
setImmediate(() => {
  console.log('immediate')
})
// poll 阶段
fs.readFile(__filename, () => {
  console.log('readFile')

  setTimeout(() => {
    console.log('timeout in I/O')
  }, 0)

  setImmediate(() => {
    console.log('immediate in I/O')
  })
})
// microtask 
Promise.resolve().then(() => {
  console.log('promise')
})
// microtask 优先级高于promise
process.nextTick(() => {
  console.log('nextTick')
})

console.log('end')
执行步骤拆解:
  1. 同步代码执行

    • 先执行console.log('start'),输出start
    • 遇到setTimeout(timers 阶段)、setImmediate(check 阶段)、fs.readFile(poll 阶段),均为异步任务,放入对应队列;
    • 遇到Promise.resolve().then()process.nextTick(),放入微任务队列;
    • 执行console.log('end'),输出end
  2. 同步代码执行完毕,清空微任务队列

    • 先执行process.nextTick,输出nextTick
    • 再执行 Promise 微任务,输出promise
  3. 进入事件循环的 timers 阶段

    • 执行setTimeout(fn, 0)的回调,输出timeout(注:若程序启动耗时极短,timers 阶段会优先执行;若耗时稍长,可能先进入 poll 阶段)。
  4. 进入 poll 阶段

    • 等待fs.readFile执行完成,触发回调函数,输出readFile
    • 回调内的setTimeout(timers)和setImmediate(check)被加入对应队列;
    • 因 poll 阶段执行完 I/O 回调后,会优先跳转到 check 阶段,因此先执行setImmediate,输出immediate in I/O
    • 下一轮事件循环的 timers 阶段,执行setTimeout回调,输出timeout in I/O
  5. check 阶段

    • 执行最外层的setImmediate回调,输出immediate(注:若 timers 阶段先执行,check 阶段会后执行)。
最终输出顺序(核心逻辑):
start
end
nextTick
promise
timeout
immediate
readFile
immediate in I/O
timeout in I/O

(注:timeoutimmediate的顺序可能因程序启动耗时略有波动,但 I/O 回调内的immediate in I/O一定早于timeout in I/O

三、理解事件循环的实践意义

掌握 Node.js 事件循环机制,是写出高性能异步代码的关键:

  1. 避免异步任务执行顺序问题:例如明确process.nextTick和 Promise 的优先级,避免回调执行顺序不符合预期;
  2. 优化性能:理解 poll 阶段的阻塞逻辑,避免 I/O 任务堆积导致事件循环卡顿;
  3. 排查问题:定位异步代码的执行延迟、内存泄漏等问题,例如区分 timers 和 check 阶段的任务调度差异。

总结

Node.js 凭借异步无阻塞、单线程高并发的特性,以及丰富的生态(从轻量框架 Express 到企业级框架 NestJS,从基础文件操作到 AI Agent 开发),成为全栈开发的核心技术。而事件循环作为其异步模型的核心,决定了异步任务的执行顺序,理解其阶段划分、微任务优先级,才能真正发挥 Node.js 的高并发优势。无论是接口转发、BFF 层开发,还是 AI 网关、Agent 系统搭建,掌握 Node.js 的核心特性与事件循环机制,都是开发者必备的能力。

Spec Kit:让 AI 编程从 Vibe Coding 到 Spec First

Hi~大家好呀,我是清汤饺子。

先说个我踩过不止一次的坑。

我要加个功能,跟 AI 说"加个用户登录"。AI 热情开干,半小时后给我整了个完整的 Auth0 集成方案——OAuth 2.0、JWT、refresh token,应有尽有。

我只想要一个最简单的本地账号密码登录。

AI 很委屈:你不就说"加个用户登录"吗?我以为……

我:我也以为你能读懂我的心思。

问题出在哪?不是 AI 不够努力,是我们在起点就没有对齐"做什么"。

然后我发现了 Spec Kit——GitHub 官方的 Spec-Driven Development 工具包。

GitHub 说:为什么不反过来试试?让规格说明书变成可执行的,代码来服务规格,而不是规格服务代码。 听起来很简单对吧?但背后的思路很根本。

一、Spec-Driven Development:规格说明书才是老大

传统开发里,代码是老大,规格说明书是跟班。 我们写 PRD、设计文档、技术方案,这些是"脚手架"——搭完就扔。代码往前走,文档跟不上,最后代码就是规格,规格就是代码。 这句话听起来很熟悉对吧?每个团队都经历过"代码改了三版,文档还停在 v0.1"的痛苦。

我们写 PRD、设计文档、技术方案,这些是" scaffolding "——搭完就扔。代码往前走,文档跟不上,最后代码就是规格,规格就是代码。

这是 GitHub 想翻过来的。

Spec-Driven Development( SDD 的核心主张是:规格说明书变成可执行的,代码是规格的输出,而不是规格的指导。

翻译成人话:以前是"我想清楚,然后写代码";现在是"我想清楚,写规格,AI 从规格生成代码"。

这不是在改进写代码的方式,这是在重新定义谁来当老大

二、Spec Kit 是什么

GitHub 官方出的,听起来就很靠谱对吧? 实际上也确实靠谱——GitHub 的工程团队自己就在用这套方法论来开发产品,所以 Spec Kit 不是纸上谈兵,是从自己的真实工作里提炼出来的

定位:让你聚焦在"产品场景和可预期结果"上,而不是从零开始 vibe coding 每一个细节。

它提供一套结构化的工作流,让 AI 从"你说什么我做什么"变成"你说什么——我来确保做对"。

支持 Claude Code、Codex、Cursor 等主流 AI 编程工具。

三、六步工作流(核心)

整个工作流的设计思路很清晰:先定规矩 → 说清楚做什么 → 给技术方案 → 拆成小任务 → 执行 → 随时检查。每一步都有 slash command 对应,不用记复杂参数,AI 帮你串联全程。

1. Constitution —— 定规矩

/speckit.constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements

这一步创建项目的治理原则:代码质量标准、测试要求、UX 一致性规范、性能指标。

这些原则会指导后续所有的开发工作,相当于给 AI 定下了"宪法"。

2. Specify —— 写规格

这是最重要的一步,也是 Spec Kit 和普通 AI 编程最大的区别。你只管说"做什么",技术细节完全不用操心。 你会发现——当你只说"做什么"的时候,AI 反而能更好地理解你的意图,不会自己脑补一堆"你可能想要"的功能。这大概就是传说中的少即是多吧。

/speckit.specify Build an application that can help me organize my photos in separate photo albums. Albums are grouped by date and can be re-organized by dragging and dropping on the main page. Albums are never in other nested albums. Within each album, photos are previewed in a tile-like interface.

这一步描述你要做什么。聚焦在"做什么"和"为什么",不要管技术栈。

你会发现——当你只说"做什么"的时候,AI 反而能更好地理解你的意图,不会自己脑补一堆"你可能想要"的功能。

3. Plan —— 给方案

/speckit.plan The application uses Vite with minimal number of libraries. Use vanilla HTML, CSS, and JavaScript as much as possible. Images are not uploaded anywhere and metadata is stored in a local SQLite database.

规格确认后,这一步给出技术方案:选什么框架、用什么数据库、依赖怎么管理。

Plan 是规格和技术决策之间的桥梁——规格说"要一个照片应用",Plan 说"用 Vite + SQLite + 原生 JS 来实现"。

4. Tasks —— 拆任务

/speckit.tasks

AI 根据 Plan 自动拆解成可执行的任务清单。每个任务精确到文件路径、验收标准,完成一个打一个勾。

5. Implement —— 执行

/speckit.implement

AI 按照任务清单一个个执行。和 OpenSpec 一样,严格按照 Plan 来,不会自己跑偏

6. Review & Iterate —— 检查和迭代

实现过程中可以随时回到前面的步骤调整。规格改了,Plan 自动更新,Tasks 自动重新生成。

四、和 OpenSpec 的区别

这是大家最常问的问题。

OpenSpec Spec Kit
出品方 Fission-AI GitHub 官方
工作流 propose → apply → archive constitution → specify → plan → tasks → implement
风格 轻量、迭代友好 正式、有"宪法"概念
团队协作 支持 更好(支持 branch + merge 规格版本管理)
社区扩展 较少 丰富(Jira、Azure DevOps 等集成)

简单说:OpenSpec 更轻量,适合个人开发者快速上手;Spec Kit 更完整,适合团队协作和有正式流程要求的项目。

五、社区扩展生态(这是亮点)

这是我觉得 Spec Kit 最值得期待的地方——生态正在生长,而且很有意思。 截止目前社区已经贡献了十几种扩展,挑几个我,觉得特别有想法的说说:

集成类

  • Jira Integration:把 spec 规格自动同步到 Jira Epics 和 Stories
  • Azure DevOps Integration:同步到 Azure DevOps Work Items

代码质量类

  • Checkpoint Extension:实现中途提交,不至于最后只有一个巨大的 commit
  • Cleanup Extension:实现完成后自动 review 改动,修小问题,标记中等问题,生成大问题报告

流程类

  • Fleet Orchestrator:全生命周期编排,带 human-in-the-loop 关卡
  • Cognitive Squad:多智能体认知系统,理解→内化→应用

文档类

  • Archive Extension:合并后归档到项目记忆
  • DocGuard:Canonical-Driven Development 文档校验和评分

这些扩展开箱即用,按需安装。

六、怎么装

# 推荐:用 uv 持久安装(稳定版)
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@vX.Y.Z

# 或者一次性运行(不需要安装)
uvx --from git+https://github.com/github/spec-kit.git@vX.Y.Z specify init <PROJECT_NAME>

⚠️ 前提:需要先安装 uv(一个极快的 Python 包管理器)。如果没装过,执行这一行就行:

curl -LsSf https://astral.sh/uv/install.sh | sh

装好后,在项目目录运行 specify init . --ai claude,AI 助手会自动识别并加载 Spec Kit。

七、技术原理

如果你对"它怎么做到的"感兴趣,这节简单说说。不感兴趣可以跳过,不影响你用它。

Spec Kit 的核心架构分四层,理解起来很简单:

  • Specify CLI:命令行工具,负责初始化项目、管理扩展、调度 AI Agent。是整个工具的入口。

  • Slash Commands/speckit.*):用户和 AI 交互的主要方式。每个 command 对应一个工作流阶段,AI 根据当前阶段决定下一步。

  • Spec Artifacts:规格说明书的输出格式。包含:

    • SPEC.md——产品需求文档
    • PLAN.md——技术实现方案
    • TASKS.md——任务清单
    • CONSTITUTION.md——项目治理原则
  • Extension System:社区扩展的插拔机制。扩展分为五类:

    • docs——读写/校验/生成规格文档
    • code——review/校验/修改代码
    • process——跨阶段工作流编排
    • integration——同步外部平台
    • visibility——项目健康度报告

八、和 Superpowers + ECC 的关系

这是 Claude skills 第四篇了,终于可以凑齐一桌麻将:

工具 出品 解决的问题
Spec Kit GitHub 官方 规格说明书 → 可执行,需求对齐
OpenSpec Fission-AI 轻量规格流程,迭代友好
Superpowers @obra 工程纪律,TDD + task 分解
ECC @affaan-m AI 性能和记忆,Token 优化

Spec Kit 和 OpenSpec 解决的是同一类问题(需求对齐),但 Spec Kit 更适合团队、更正式;OpenSpec 更轻量、更迭代。

Superpowers 在下游执行层——规格确认后,怎么按工程规范来做。

ECC 在底层——让 AI 跑得更稳、更快、更聪明。

四者组合,才是完整的 AI 编程工作流。


写在最后

用了 Spec Kit 之后,我最大的感受是: Spec Kit 把"我以为 AI 能读懂我"变成了"我明明白白告诉 AI 我要什么"。

这种感觉就像是——以前是让 AI 猜你的心思,现在是跟一个聪明但需要你说清楚的同事合作。累吗?稍微累一点,但返工少多了,信心也多了

Spec Kit 解决的就是这个问题——在 AI 动笔之前,先把"要什么"定义清楚。

GitHub 官方的背书也意味着它的长期维护有保障,扩展生态会更丰富。

如果你在团队里推动 AI 编程,用 GitHub 官方出品的工具,说服成本会低很多——不用解释这个工具靠不靠谱,直接说"这是 GitHub 官方做的"就够了。

当然,它也有学习成本。六个 command、constitution 怎么写、plan 怎么给,都需要摸索。但这个成本是一次性的,投入产出比很高。

你试过 Spec Kit 吗?或者你更偏向 OpenSpec 的轻量路线?欢迎评论区聊聊。

如果觉得有帮助,点个赞收藏一下,我会有更多动力继续写这个系列。

也欢迎关注我的公众号「清汤饺子」,获取更多技术干货!

本地执行 IPA 混淆 无需上传致云端且不修改工程的方案

在很多团队里,混淆这一步常常被外包给在线加固服务:上传 IPA,等结果,下载再签名。流程确实顺手,但当项目涉及商业逻辑或私有算法时,这种方式总让人有点不踏实——完整的二进制、资源、接口结构都离开了本地环境。

后来我们把这一步彻底改成本地执行,不上传任何文件不改工程源码只操作已编译好的 IPA


一、先确认 IPA 当前长什么样

把构建好的 IPA 复制一份并解压:

unzip app.ipa

进入目录:

Payload/App.app

检查三个位置:

1)二进制可读信息

strings AppBinary | head

如果能看到:

UserManager
PaymentService
VipController

说明符号没有做处理。


2)资源目录结构

assets/images/vip_banner.png
config/payment.json

路径本身已经带有业务语义。


3)前端资源

main.jsbundle
index.html

这些文件如果未压缩,直接可读。


二、本地链路的核心思路

整个流程不依赖任何远程服务,结构如下:

IPA 文件
→ 本地解析
→ 本地混淆
→ 本地资源处理
→ 本地签名
→ 本地测试

关键在于:所有操作都发生在开发机器上。


先处理 JS / H5(如果存在)

如果项目中包含 WebView 或 React Native 模块,可以在 IPA 处理前压缩脚本。

例如:

terser main.js -o main.min.js

或者:

uglifyjs page.js -o page.min.js

压缩后再替换回 IPA 资源目录。

这样可以先降低 JS 层的可读性。


在本地执行 IPA 符号混淆

这一步是核心。

使用 Ipa Guard 这类本地运行的 IPA 混淆工具,可以直接处理 Mach-O 文件,而不需要源码。

操作过程:

  • 打开工具
  • 导入 IPA
  • 进入「代码模块」

可以看到:

OC 类
Swift 类
OC 方法
Swift 方法

在列表中选择需要处理的符号,例如:

UserManager
PaymentHandler
VipService

执行后:

UserManager → k39sd2

整个过程在本地完成,不会上传任何数据。


资源文件本地重写

继续在 Ipa Guard 的资源模块中操作。

勾选:

  • 图片
  • JSON
  • HTML
  • JS

执行后:

vip_banner.png → a82kd.png
payment.json → x92ks.json

工具会自动更新引用路径。

这一层的作用是让资源结构失去语义。


改变资源指纹(避免“同源识别”)

如果多个应用使用相同资源,文件内容会成为识别依据。

在 Ipa Guard 中开启 MD5 修改:

md5 banner.png

处理前后不同。

文件视觉效果不变,但指纹已经改变。


清理调试信息

检查:

strings AppBinary | grep NSLog

如果存在日志或调试字符串,可以在混淆阶段删除。

Ipa Guard 提供调试信息清理选项。


补充一个“简单校验机制”

为了避免 IPA 被二次篡改,可以在原生层加入简单校验:

  • 计算关键文件 hash
  • 启动时验证

例如:

if hash != expected { exit(0) }

这一步不依赖混淆工具,但可以作为补充。


本地完成签名与安装

混淆后 IPA 已失去原签名,需要重新签名。

可以使用:

kxsign sign app.ipa \
-c cert.p12 \
-p password \
-m dev.mobileprovision \
-z test.ipa \
-i

或者直接在 Ipa Guard 中配置证书。

连接设备后可以直接安装。


验证结果(这一步不能跳)

安装后重点检查:

  • 页面是否正常
  • 资源是否加载
  • 动态调用是否正常
  • WebView 内容是否可用

如果出现异常,通常是:

  • 某些符号被误混淆
  • 某些资源路径未正确更新

把 IPA 混淆完全放在本地执行,并不只是“更安全”的选择,它还带来一个实际好处:每一步都可控、可调试、可回滚。相比上传到云端处理,本地流程更适合需要长期维护的项目。

搭建一个云端Skills系统,随时随地记录TikTok爆款

最近 Claude Skills 很火。

但我观察了一圈,发现大家都在陷入一种“开发者的自嗨”。

绝大多数 Skills 的应用场景都被死死锁在 IDE 里,锁在开发者的电脑前。

这叫开发提效,不叫业务提效。

真正的业务发生在移动端,发生在你通勤、吃饭、甚至躺在床上刷 TikTok 的时候。

如果你的 AI 能力必须打开电脑、输入命令行才能调用,那它的时空效率就是零。

于是我抛弃本地的 Claude Code,基于 OpenHands 做了一套云端 Skills 系统。

效果极其简单粗暴:

我在刷 TikTok,看到一个爆款视频,点击复制链接,敲击 iPhone 背面三下。

wxv_4355007050494509070

20 秒后,我的飞书多维表格里自动新增了一行数据。

Image

这行数据包含了:这个视频的无水印文件、Gemini 拆解的镜头语言分析、爆款原因推导,以及一套可直接复用的 AI 视频生成提示词。

全过程我不需要打开电脑,不需要切换 APP,不需要等待。

这就是我今天要聊的:如何用 OpenHands + Skills + iOS 快捷指令,构建一套真正落地的业务自动化系统。

01 为什么 Claude Code 在业务侧是伪需求

先厘清两个概念:OpenHands 和 Claude Code。

Claude Code 是 Anthropic 官方推出的命令行工具,它是一个嵌入在你本地终端里的结对程序员。它的 Skills 本质是上下文记忆和本地工具接口。

它的优势是懂你的代码规范,能直接改你电脑里的文件。

但它有一个对于业务场景的致命弱点:它必须依附于你的会话,你不在,它就不动。

它是一个副驾驶(Copilot)。

而 OpenHands(前身 OpenDevin)是一个开源的、自主的 AI 软件工程师。它运行在 Docker 容器里,是一个独立的服务端 Agent。

Image

openhands.dev/

它是一个可以被封装成 API 服务的数字员工。

我看重 OpenHands 的核心理由只有一个:它可以 24 小时在线,并且可以通过 API 远程唤醒。

我做的这个 TikTok 分析系统,本质就是把 OpenHands 部署在服务器上,通过 FastAPI 暴露接口。

Claude Code 是给你用的工具;OpenHands 是你雇佣的、随时待命的员工。

🐵

小提示:FastAPI 的服务地址后加/docs就是文档了

02 业务视角:从 刷视频 到「数据入库」的闭环

对于做出海营销和短视频矩阵的朋友,拆解爆款是每天的必修课。

传统的流程极其反人类:

  1. 1. 手机刷到视频,点收藏。
  2. 2. 晚上回家打开电脑,把链接导出来。
  3. 3. 找第三方工具去水印下载。
  4. 4. 把视频传给 Gemini 分析。
  5. 5. 人工把分析结果复制粘贴到 Excel 或飞书。

这个链路太长,断点太多。任何需要延迟满足的流程,最终都会变成不了了之。

我的远程 Skills 方案,把这个流程压缩到了极致。

整个逻辑是这样的:

Image

用户端(前端)

利用 iOS 自带的快捷指令 + 背部轻点功能。

  • 动作:获取剪贴板内容(TikTok 链接)。
  • 触发:发送 HTTP POST 请求给我的服务器。
  • 反馈:手机震动一下,表示任务已接收。

Image

Image

服务端(后端)

OpenHands 接收到请求后,自主执行以下 Skills:

  1. Playwright Skill:

启动无头浏览器。这里有一个技术难点,TikTok 的反爬虫机制非常严格。如果用普通的 request 请求,成功率几乎为零。OpenHands 调用 Playwright 模拟真实浏览器行为,绕过 blob 协议,抓取真实的 MP4 视频流。这种方式的下载成功率稳定在 70%-80%

  1. Gemini Skill:

视频下载后,调用Gemini 2.5 Flash,快且便宜。它不只是看,它是理解。它可以识别拍摄角度(俯拍/特写)、运镜方式(推拉摇移)、BGM 节奏点、色彩心理学。

  1. Feishu Skill:

将清洗好的结构化数据(JSON),通过 API 写入飞书多维表格。

结果:

当你刷完半小时视频,打开飞书,几十个爆款视频的深度分析报告已经整整齐齐躺在那里了。

这才是 AI 赋能业务的本质:隐形化。

Image

Openhands 的 Skills 文档:

docs.openhands.dev/sdk/guides/…

03 举一反三:跨境电商的远程 Skills 玩法

这套架构的核心逻辑是:移动端触发 -> 服务端 API -> OpenHands 执行复杂 Skills -> 结果回传。

这个逻辑在出海业务里有无限的延展性。

我给几个具体的场景,你们可以拿去直接落地。

场景一:竞品独立站监控

  • 动作:在手机浏览器看到竞品的 Shopify 店铺,复制链接,触发 Shortcut。
  • Skills:OpenHands 调起爬虫 Skill 扫描该站点的新品上架情况、价格策略,并调用 SEO Skill 分析其关键词布局。
  • 产出:一份竞品分析简报直接推送到你的 Slack 或 钉钉。

场景二:亚马逊差评自动预警与回复草稿

  • 动作:系统监控到差评(自动触发,无需人工)。
  • Skills:OpenHands 读取差评内容,结合历史客服知识库 Skill,分析用户情绪,并模仿金牌客服的语气撰写 3 个版本的回复邮件。
  • 产出:草稿进入审核流,你只需要在手机上点批准。

场景三:广告素材批量生产

  • 动作:上传一张产品图到指定文件夹。
  • Skills:OpenHands 识别产品特征,调用 Midjourney 或 Runway 的 API,结合当下的流行趋势 Skill,自动生成 10 种不同风格的广告背景图。
  • 产出:素材自动同步到 Google Drive 供投放团队筛选。

04 为什么非要用 Agent Skills?写个 Python 脚本不行吗?

这是很多技术出身的朋友最容易陷入的误区。

你这个功能,我写个 Python 脚本 + 定时任务也能跑,为什么要搞这么复杂的 OpenHands Skills?

因为业务逻辑是流动的,而脚本是僵死的。

如果你写死了一个 Python 脚本:

  • 当 TikTok 的前端代码更新了 class 名,脚本报错,你得去修。
  • 当飞书的 API 接口变动,脚本报错,你得去修。
  • 当 Gemini 的模型参数调整,脚本报错,你得去修。

但在 OpenHands Skills 的架构下,我们定义的不是步骤,而是目标。

在我的 Skill 定义里,我告诉 OpenHands:你的任务是下载这个页面上的视频,如果常规方法失败,尝试模拟用户滚动;如果还失败,检查是否有验证码并尝试通过。

OpenHands 作为一个 Agent,它具备自主决策和自我修复的能力。

  • 它发现 TikTok 改了页面结构?它会尝试用视觉识别去定位播放按钮。
  • 它发现 API 报错?它会自主查阅文档或尝试备用节点。

在跨境出海这种平台规则朝令夕改的环境下,维护脚本的成本极高。

我们需要的是一个能够理解意图并自主寻找路径的智能体。

05 思路打开,Agentic Skills 的高级玩法

文章到这里,这套远程 Skills 系统的雏形已经搭建完毕。

但如果你觉得这就结束了,那你就小看了 Agentic Skills 的天花板。

我们现在的架构是“一个请求触发一个 Skill”,但这只是冰山一角。真正的威力在于 Multi-Skill Orchestration(多技能编排)。

  1. 1. Skill Chain(技能链)与递归调用

OpenHands 的 Skill 本质是可执行的逻辑单元。我们可以像写代码一样,让 Skill A 去调用 Skill B。

  • 比如定义一个 Base-Skill:只负责做基础的数据清洗。
  • 再定义一个 Pro-Skill:先调用 Base-Skill 处理数据,再把结果传给 Analysis-Skill,最后调用 Report-Skill 生成报告。

你可以构建一个自我迭代的 Agent。让它先写一段代码(Coding Skill),然后自己运行测试(Testing Skill),如果报错,递归调用 Coding Skill 进行修复,直到测试通过。

  1. 混合云架构(Hybrid Agent Architecture)

OpenHands 运行在 Docker 里,这意味着它可以部署在任何地方。

  • 私有化部署:对于涉及公司财务、用户隐私的数据,你可以把 OpenHands 部署在公司内网服务器上。
  • 公有云调用:对于需要访问外网(如 TikTok 下载、竞品分析)的任务,部署在 AWS 或 Vercel 上。

这样,通过 API 网关,你可以指挥内网的 Agent 去调用外网的 Agent,实现数据在安全域和互联网域之间的智能流转。

  1. “人机回环”的异步交互

谁说 API 只有“请求-响应”这一种模式? 在我的系统中,有些复杂任务(如竞品深度调研)可能需要运行 30 分钟。

  • 流程设计:OpenHands 接收任务 -> 立即返回 TaskID -> 后台异步执行。
  • 关键点:当 Agent 遇到无法决策的卡点(例如:这个验证码我解不开,或者这个竞品网站有两套价格体系,取哪套?),它可以主动通过飞书/Slack 给你发消息请求确认。

你点击确认后,Agent 继续执行。这才是真正的人机协作:AI 处理海量冗余信息,人类只在关键节点做决策。

在这个体系下,Skills 不再是静态的脚本,而是可生长、可组合的原子能力。

未来,你的个人服务器里可能运行着上百个这样的 Skills。它们是一群田螺姑娘,在你睡觉的时候,帮你监控市场、回复邮件、整理知识、优化代码。

而你,只需要握着手机,轻轻敲两下背部,就像魔法师挥动了魔杖。

这,才是 Agent 时代的真正玩法。

❌