日习一技 - 2026年6月
type: channel
link: https://i-study-everyday.online/2026/06/
description: 每天学一点新技术，新框架，新工具，新名词。关于编程语言，数据库，数据结构，算法，机器学习，云计算。
items: 给小龙虾配个浏览器：学习 browser 工具
items: 学习 turbovec 的 SIMD 搜索内核
items: 学习 turbovec 的量化算法
items: 学习 turbovec 的混合检索与框架集成
items: turbovec 快速入门
items: 让小龙虾自己写手册：Skill Workshop
items: 带小龙虾逛 ClawHub：自定义 Skill 实战
items: 给小龙虾写本操作手册：Skills 系统
items: 让小龙虾给 Claude Code 派活：学习 OpenClaw 的 ACP 工具
items: 给小龙虾配个浏览器：学习 browser 工具（二）

给小龙虾配个浏览器：学习 browser 工具
type: item
date: 2026-06-29T09:06:00+00:00
description: 前两篇我们把 OpenClaw 的内置工具箱挨个过了一遍，末了还留了个尾巴：其中有两个工具细节比较多，打算各开一篇单独讲，一个是浏览器工具 browser，一个是 ACP。这一篇就先来学习 OpenClaw 的 browser 工具。browser 会真的开起一个 Chromium 浏览器，像人一样去操作网页，解决的是前面 web_search、x_search、web_fetch 那套轻量工具搞不定的场景：要登录、要点按钮、内容得靠 JS 才渲染得出来的页面。它功能最强，配置也最讲究，光「浏览器跑在哪」就有 host、sandbox、node 三种情况，「用哪个浏览器」又分好几种 profile，内容实在不少，于是我们把这一个工具拆成上下两篇：这一篇先把环境铺好；下一篇再讲环境就绪之后，怎么真正驱动浏览器在页面上干活。铺垫完了，先从它那张复杂的参数表看起。工具参数定义和前面的 message、nodes 一样，浏览器在 agent 侧也只暴露 browser 一个工具，靠不同的 action 分派不同的操作：它的 action 覆盖了一整套浏览器生命周期，粗看可以分成三组：一组管「进程和环境」，比如体检环境的 doctor、起停浏览器的 start 和 stop；一组管「标签页」，列、开、切、关四件套；剩下一组才是真正在页面上干活的，导航、取结构、截图、执行动作，外加读日志、导出 PDF、传文件、处理弹窗。整理成一张图大致如下：其中 act 本身又是个小分派器，要执行的动作由 kind 指定，对应参数（推荐打包进 request 对象）随 kind 不同而不同：除这几个核心参数外，还有不少参数，基本都是配合某个具体动作用的，这里先扫一眼，后面遇到时再展开：运行位置和 exec 的 host 参数类似，browser 也有一个 target 参数，取值是 host / sandbox / node 三种。不传 target 时，OpenClaw 按会话类型挑默认值：沙箱会话默认 sandbox，非沙箱会话默认 host；要是有一台带可控浏览器的 node 连了进来，工具还可能自动路由到那台 node 上。三个取值里 host 最直白、也最常用，非沙箱会话默认就走它，你不用做任何配置，所以下面先从 host 讲起，再回头看 sandbox 和 node。在 host 上运行浏览器当 target="host" 时，就是在网关那台机器上启动或接管一个本地浏览器。但是启动哪个浏览器呢？这就要靠 profile 来确定了。OpenClaw 在 host 上内置了两个 profile：openclaw（默认）：一个专用的、隔离的 Chromium 实例；风险低，和你个人浏览器完全隔离。user：经 Chrome DevTools MCP 接管你真实的、已登录的 Chrome；风险高，在你的登录身份里操作。先说 openclaw profile，它是专门给 agent 自动化用的隔离 profile，用的是一份独立的用户数据目录，不会碰你个人浏览器的 profile；另外，它会走独立的 CDP 端口，从 18800–18899 这个区间里分配，特意避开了常见的 9222，免得和你本地开发调试时起的浏览器冲突；窗口默认还带了一圈橙色外框（#FF4500），方便用户一眼就能分辨「这是 agent 在用的浏览器，不是我自己那个」；如果本机上找不到指定的浏览器时，OpenClaw 会按 Chrome → Brave → Edge → Chromium → Chrome Canary 的顺序自动挑一个能用的。这里的 CDP 指的是 Chrome DevTools Protocol（Chrome 开发者工具协议），是 Chromium 内核对外暴露的一套远程控制接口。你平时按 F12 打开的开发者工具，背后走的就是它。各种浏览器自动化框架（像 Playwright）之所以能在外部进程里驱动浏览器导航、点击、截图，靠的都是连上这个协议端口。另一个 user profile 的思路则不同：它不自己另起一个隔离实例，而是走官方 Chrome DevTools MCP 的 attach 流程，直接接管你当下正开着的那个 Chrome，复用里面现成的标签页和登录态。它要解决的是「我已经在浏览器里登录好了某个网站，想让 agent 直接在我这个会话里接着操作」这类需求。方便归方便，代价是 agent 是货真价实地在你本人的身份下点点点。所以文档把它明确标为更高风险的路径，并且要求只在你本人就坐在电脑前、能亲手点掉那个 attach 授权弹窗的时候才用；它也只负责 attach，绝不会替你去启动浏览器。有一个容易被忽略的限制：user 这类接管现有会话的 prof…

学习 turbovec 的 SIMD 搜索内核
type: item
date: 2026-06-28T23:59:00+00:00
description: 在上一篇中，我们沿着 encode.rs 把量化算法走了一遍：随机旋转把每个坐标压到 Beta 分布上，Lloyd-Max 码本求出最优的标量量化器，TQ+ 校准做 5/95% 分位对齐，长度归一化修正补回内积的系统性偏差。这一整套流程解决的是精度和内存的问题，一个 1536 维的 float32 向量被压成 4-bit 编码，体积缩小到原来的八分之一，Recall@1 还能稳住甚至反超 FAISS。不过压缩只是故事的一半。向量被压成了一串 4-bit 编码以后，搜索的时候这些编码到底是怎么被高速扫描的？10 万条向量、每条 1536 维、4-bit，落到内存里是 73.6 MB 的紧凑字节流，一次查询要在这上面算出 top-k。turbovec 之所以能在 Apple M3 Max 上单线程跑到约 2.0 ms、比 FAISS IndexPQFastScan 快 19%，靠的就是 search.rs 里那套手写 SIMD 内核。今天我们就来读这部分代码，把整个搜索内核拆开看。搜索路径全景先看一次 search 调用都发生了什么。代码入口在 lib.rs 的 search_with_mask，真正的计算委托给 search.rs 的 search 函数。整条路径如下：这几步都对应 search.rs 里的一段代码，我们顺着往下走。第一步：批量旋转上一篇讲过，库向量在编码时都乘过一个随机正交矩阵 Q，它是数据无关的常量，放在 OnceLock 缓存里，整个索引共用一份。库向量活在旋转后的坐标系里，查询自然也得旋转到同一个坐标系，内积才算得对。所以这里把查询也乘上同一个 Q，就是 search 函数开头的一次 GEMM：// q_ref：把输入 queries 按行包成查询矩阵（nq 条、每条 dim 维） let q_ref = faer::mat::from_row_major_slice::<f32, _, _>(queries, nq, dim); // r_ref：同样包好的旋转矩阵 rotation let r_ref = faer::mat::from_row_major_slice::<f32, _, _>(rotation, dim, dim); // out_mut 是输出 q_rot；matmul 把 q_ref 乘上 r_ref.transpose()（即 rotation^T）写进去 // 整句就是 q_rot = queries @ rotation^T faer::linalg::matmul::matmul( out_mut, q_ref, r_ref.transpose(), None, 1.0_f32, faer::Parallelism::Rayon(0), );关键在于这里是把所有查询堆成一个矩阵、和旋转矩阵做一次 GEMM，而不是逐查询做矩阵向量乘。这样能把 FMA 吞吐喂满，缓存里那一份旋转矩阵也在多条查询间复用。GEMM（General Matrix Multiply，通用矩阵乘）是线性代数库里最核心的操作，算的就是两个矩阵相乘 C = A × B。把多条查询堆成一个矩阵、和旋转矩阵一次乘完，比逐条做矩阵向量乘更能榨干 CPU 的缓存和向量单元，是 BLAS 这类库重点优化的对象。FMA（Fused Multiply-Add，融合乘加）则是把一次乘法和一次加法合并成单条 CPU 指令，一次算出 a × b + c，既省一条指令又少一次中间舍入；矩阵乘的内层全是乘加，所以 FMA 的吞吐基本就决定了 GEMM 的速度。第二步：TQ+ 逆校准这一步同样是上一篇的对偶操作。编码时 TQ+ 对每个坐标做过一次仿射 (shift, scale)，把库向量的经验分布拉回理论 Beta；库向量既然被这样变换过，查询侧就要施加配套的逆变换，才能让两边在校准后的空间里算出和原来一致的内积。干这件事的是 calibrate_queries，它对每条查询走下面这个循环：// q_row：这条旋转后的查询；calib_row：要写出去的校准后查询；bc：正在累加的偏置 for d in 0..dim { // tqplus_scale[d] 是编码时给该坐标乘过的缩放，逆过来就是除以它 calib_row[d] = q_row[d] / tqplus_scale[d]; // tqplus_shift[d] 是编码时加过的平移，在内积里只贡献一个与库向量无关的常数项， // 不必逐坐标改查询，把这些常数全累加进 bc 即可 bc -= (q_row[d] as f64) * (tqplus_shift[d] as f64); } // 循环结束，bc 就是这条查询的偏置修正 bias_corr *bias =…

学习 turbovec 的量化算法
type: item
date: 2026-06-26T00:14:00+00:00
description: 前两篇我们把 turbovec 的 API 过了一遍：第 1 篇跑通了 add 和 search，第 2 篇学了混合检索和框架集成。它们都接口层面，但有一个核心问题一直没回答：为什么 turbovec 不需要训练就能把向量压到 2-4 bit，还能在召回上追平甚至超过 FAISS 的 PQ？答案在它背后那套免训练量化算法 TurboQuant。这个算法来自 Google Research 的论文，今年初刚被机器学习顶会 ICLR 接收，turbovec 是它第一个公开的工程实现。今天我们就从传统量化的负担讲起，再读论文的核心思想，最后对照 rotation.rs、codebook.rs、encode.rs 三个文件，把整条编码管道从头到尾理清楚。传统量化的训练负担第 1 篇我们算过那笔内存账：1536 维 float32 一条向量约 6 KB，千万级语料就是 31 GB。量化（Quantization）做的就是用更少的比特去近似表示原本的浮点数，把每个坐标从 32 bit 压到 2-4 bit，体积掉到 1/8 到 1/16，单机就能装下，代价是牺牲一点精度。问题在于，怎么压才能让精度损失尽量小。向量量化最经典的方案是乘积量化（Product Quantization，简称 PQ），2011 年由 Jégou 等人提出，FAISS 里的 IndexPQ、IndexPQFastScan 都是它的实现。PQ 的思路是分段查表。把一条 1536 维向量切成若干个子段，比如 8 段，每段 192 维；对每一段，用 k-means 在训练数据上聚出 256 个聚类中心，存成一张码本（codebook）。编码时，每段只记录它最近的那个中心的下标，一个 8 bit 的整数就够了。原本 192 个 float32 的子段，压成了 1 个字节。整个分段查表的过程如下图所示：这套方案压缩率很高，但有一个绕不开的前提：码本要从数据里学出来。这就带来两个负担：训练阶段不可省：用 FAISS 的 PQ，必须先准备一批有代表性的向量，调用 index.train(xs) 跑 k-means 训练码本，之后才能调用 add 添加向量。数据量不足时训练不出高质量的码本。数据漂移要重训：码本是对当前数据分布的拟合。如果后续加进来的向量分布发生变化，比如更换了 embedding 模型、业务语料迁移，旧码本就不再贴合，召回率随之下降，需要重新训练并重新编码整个索引库。对于一个需要支持在线增量写入的向量库来说，这个训练环节是个明显的负担。我们希望第一条向量进来就能直接编码、直接检索，不必经历积累数据、训练、回填这一整套流程。这就引出了另一条路线：数据无关量化（data-oblivious quantization）。它的目标是设计一个不依赖具体数据分布的量化器，码本可以提前算好、写死在代码里，任何数据进来都用同一套。TurboQuant 走的正是这条路。TurboQuant 的核心思想TurboQuant 论文的全名是《TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate》，arXiv 编号 2504.19874，去年 4 月提交，今年初被 ICLR 接收。它要解决的问题是：在不看数据分布的前提下，怎么设计一个接近最优的标量量化器。它的关键观察分三步。第一步，随机正交旋转。对每一条归一化后的单位向量，乘上一个随机生成的正交矩阵。正交变换不改变向量长度，也不改变向量之间的内积，所以旋转之后做的所有检索运算，结果和旋转前等价。旋转只是换了一组坐标基。第二步，旋转后坐标服从已知分布。这是整个算法的支点。一个 d 维单位向量随机旋转之后，它的每个坐标不再是任意的，而是服从一个确定的 Beta 分布。坐标的取值被摊平到了一个集中、对称、与数据无关的形状上。原始数据可能在某些维度上特别集中、某些维度上特别分散，但随机旋转把这种各向异性打散了，每个坐标看起来都像是从同一个分布里采出来的。第三步，高维下坐标近似独立，可以逐坐标独立量化。维度越高，不同坐标之间的相关性越弱，近似相互独立。既然每个坐标都同分布、又近似独立，那就不需要像 PQ 那样为不同子段学不同的码本了。我们只要针对这一个 Beta 分布，求出一个最优的标量量化器，然后对所有坐标用同一套量化器即可。这套量化器是离线就能算好的，因为 Beta 分布的形状只由维度 d 决定，和数据没有半点关系。论文给出的理论保证是：这样做的失真率（distortion rate）落在信息论下限，也就是香农下界的约 2.7 倍以内。一个完全不看数据、提前写死的量化器，做到了接近理论最优的精度。下面我们把随机正交旋转、Beta 分布、求最优量化器用的…

学习 turbovec 的混合检索与框架集成
type: item
date: 2026-06-22T00:50:00+00:00
description: 昨天我们快速入门了 turbovec，体验了 TurboQuantIndex 的 add / search / write / load 等基本接口，也简单认识了带稳定 ID 的 IdMapIndex，当时提过一句，它是后面做混合检索过滤的基础。不过当时只是照着接口跑了一遍，没有解释为什么 TurboQuantIndex 的下标不能当业务主键用，也没讲这层 ID 映射是怎么实现的，更没涉及按租户、按权限过滤这样的真实检索场景。今天我们就把这些坑填上：先从 TurboQuantIndex 删除时的下标移动说起，弄清楚 IdMapIndex 的实现，再体验过滤搜索和两阶段混合检索，最后是四大框架的开箱集成。TurboQuantIndex 的下标移动问题先看 TurboQuantIndex 的删除方法 swap_remove。它的命名直接对标 Rust 标准库的 Vec::swap_remove：把最后一个向量挪进被删的槽位，再把长度减一。这样删除是 O(1) 的，代价是顺序不保证。之所以这么设计，是因为所有向量的压缩编码紧挨着存在一个连续数组里，槽位号就是存储位置。从中间删一条，要么把后面的数据整体前移，复杂度 O(n)，删得越靠前越慢；要么留下空洞，破坏检索时批量打分依赖的致密布局。而把最后一条搬过来补位，只需要拷贝一条向量的数据，数组始终致密。举个例子，假设索引里有 6 个向量，槽位编号 0 到 5。当我们 swap_remove(2) 之后，原来在槽位 5 的向量会被搬到槽位 2：idx = TurboQuantIndex(dim=1536, bit_width=4) idx.add(vectors) # 灌入 6 个向量，槽位 0 到 5 moved_from = idx.swap_remove(2) # 槽位 5 的向量补到槽位 2 # moved_from == 5问题来了。如果你的业务库里用槽位号当外键，比如数据库某行记了 vector_slot = 5，删除后这一行指向的就不再是原来的向量了。search 返回的也是槽位下标，下一次查询拿到的 indices 跟你之前缓存的对应关系全乱套。简单来说，TurboQuantIndex 的下标是位置性的，适合纯追加、不删除的场景，不适合拿来当业务主键。要在删除频繁的系统里稳定地标识一个向量，得换一个工具。用 IdMapIndex 解决下标移动IdMapIndex 就是为这个场景设计的。它在 TurboQuantIndex 外面包了一层双向映射：一边是你自己定的业务 ID（64 位无符号整数），一边是向量实际所在的槽位，两边可以互查。调用方始终用业务 ID 来标识向量，其他向量的增删不会改变这个 ID。官方文档把它类比成 FAISS 的 IndexIDMap2，哈希表支撑、按 ID O(1) 删除，熟悉 FAISS 的话可以直接对应过来。它的设计思路在 id_map.rs 的模块注释里说得很清楚：//! `IdMapIndex` wraps the positional index //! with a bidirectional `id ↔ slot` mapping so callers can identify //! vectors by a stable `u64` ID that doesn't change when other vectors //! are inserted or removed.结构体本身只持有两张表，向量存储、旋转、打分、序列化全部委托给内层的 TurboQuantIndex：pub struct IdMapIndex { inner: TurboQuantIndex, /// slot → external id slot_to_id: Vec<u64>, /// external id → slot id_to_slot: HashMap<u64, usize>, }Python 侧的基本用法上一篇已经见过，这里快速过一眼：import numpy as np from turbovec import IdMapIndex index = IdMapIndex(dim=1536, bit_width=4) index.add_with_ids(vectors, np.array([1001, 1002, 1003], dtype=np.uint64)) scores, ids = index.search(query, k=10) # 返回的是你的 uint64 外部 ID index.remove(1002) # 按 ID 删除，O(1) assert 1002 not in index # 被删的 ID 已经不在 assert …

turbovec 快速入门
type: item
date: 2026-06-19T00:59:00+00:00
description: 做 RAG 的朋友多半都算过这样一笔账：一条 768 维的 embedding 向量，用 float32 存就是 768 × 4 = 3072 字节，正好 3 KB；语料攒到 1000 万条，光向量就要占 31 GB 内存。768 维是开源模型很常见的维度，BGE 的 bge-base-zh-v1.5、Sentence Transformers 的 all-mpnet-base-v2 都是这个规格。换更高维的模型，这笔账还要成倍往上涨：BGE-M3 是 1024 维（41 GB），OpenAI 的 text-embedding-3-small 是 1536 维（61 GB），text-embedding-3-large 到了 3072 维，直接奔 123 GB 去了。对于做 RAG 的团队来说，光是把向量塞进内存就是一笔不小的开销，更别提还要留余量给检索本身。常见的解法是向量量化（vector quantization），用更少的比特近似表示每个向量，FAISS 的 Product Quantization（PQ）就是这条路线的代表。不过 PQ 是有代价的：建索引前要先用一批代表性数据训练码本，语料分布变化大了还可能要重训。今天我们要介绍的主角 —— turbovec，走了一条不一样的路。它是一个用 Rust 实现、带 Python 绑定的向量索引库，核心算法来自 Google Research 的 TurboQuant 论文（arXiv:2504.19874，已被 ICLR 2026 接收），不需要训练，也不需要重建，向量加进去就能搜。它的项目首页放了一句颇有冲击力的话：A 10 million document corpus takes 31 GB of RAM as float32. turbovec fits it in 4 GB - and searches it faster than FAISS.这里的 31 GB 正是我们开头算的那笔账：1000 万条 768 维向量，用 float32 存要占 31 GB 内存，turbovec 能把它压到 4 GB，而且检索速度比 FAISS 还快。4 GB 是这么来的：TurboQuant 算法把每个坐标从 float32 的 32 bit 量化到 2~4 bit，压缩 8~16 倍，31 GB 的数据 4-bit 量化后约 3.9 GB；如果用 2-bit 还能再减半，只剩 2 GB 不到。正是由于它内存占用小、速度又快，最近几个月接连被几家海外科技媒体报道。它的几个核心特性是：在线量化（online quantization）：添加向量即建索引，没有单独的训练阶段，也不需要随语料增长重建索引比 FAISS 更快：手写 NEON（ARM）、AVX2 / AVX-512BW（x86）三路 SIMD 内核，运行时自动选择，ARM 上所有配置比 FAISS 的 IndexPQFastScan 快 10%~19%搜索时过滤：search() 支持传入 id allowlist 或 bitmask，量化内核直接在块级别处理过滤，做混合检索时不用先取后筛纯本地运行：不依赖托管服务，数据不出本机或 VPC，配合任意开源 embedding 模型就能搭一套完全离线的 RAG 栈与 FAISS 的对比定位FAISS 是向量检索领域的老牌选手，由 Meta 开源，功能非常全。turbovec 没有想做一个大而全的库去取代 FAISS，它的对标对象很明确，就是 FAISS 里走量化快速扫描路线的 IndexPQFastScan。两者最核心的差别在于有没有训练阶段。FAISS 的 PQ 是数据相关（data-dependent）的量化。它需要先用一批代表性数据跑 train()，学出一套码本（codebook），之后才能 add() 向量、search() 查询。码本学得好不好，直接影响召回质量；语料分布变化大了，码本还可能要重学。turbovec 用的 TurboQuant 是数据无关（data-oblivious）的量化。它的思路是：先对输入向量做一次随机正交旋转，旋转之后每个坐标会服从一个集中的 Beta 分布；在高维空间里不同坐标近似独立，于是可以对每个坐标独立套用一个最优标量量化器。整个过程不依赖具体数据，所以不需要训练、不需要学码本，向量加进去就直接建好了索引。它们两在使用上的主要差别如下表所示：数据无关并不意味着 turbovec 完全不看数据。它在第一次 add() 时会做一次 TQ+ 校准，从首批向量里学一组 per-coord 的微调参数，但只学这一次，之后不再变。这部分细节我们留到后面讲量化算法的文章里展开。安装turbovec 同时面向 Python 和 Rust 用户…

让小龙虾自己写手册：Skill Workshop
type: item
date: 2026-06-15T00:31:00+00:00
description: 上一篇我们围绕自定义 skill 做了三件事：从 ClawHub 上搜索并安装别人的 skill、从零开始自己手写一份 skill、再把它发布回 ClawHub 让别人也能安装。三件事做下来你应该感觉到了，手写 skill 终究是个体力活，触发短语得自己琢磨、正文得自己组织、哪些步骤值得沉淀也得自己判断。前面提过的 skill-creator 能稍微帮你减负，但本质还是一份「按步骤填空」的写作指导，触发权和判断权都在你手里。OpenClaw 有个实验性的内置插件叫 Skill Workshop，方向反过来：每轮成功的会话结束之后，它会自动扫一遍历史消息，把里面值得沉淀的可复用流程提议成一份 workspace skill，如果安全扫描通过就能直接写盘，让小龙虾把这一轮里学到的流程顺手写进下一轮自己能用的手册。开启与配置这是个实验性插件，默认关闭，需要你在配置文件里显式打开才会加载。打开 OpenClaw 的配置文件，在 plugins.entries 下加上这么一段：{ plugins: { entries: { "skill-workshop": { "enabled": true, "config": { "autoCapture": true, "approvalPolicy": "pending", "reviewMode": "hybrid" } } } } }改完保存后，再运行 openclaw gateway restart 重启网关让它生效。这几个配置参数逐个看一下：autoCapture: true —— 让它在每轮成功对话结束后，自动扫一遍历史消息找可沉淀的东西；关掉的话它就不主动扫了，只有你开口让小龙虾「把这个存成 skill」时才会存。approvalPolicy: "pending" —— 它扫到觉得该存的东西时，不会直接写成文件，而是先攒成一条「提议」排进待办队列，等你审批通过后才真正落到磁盘上。建议先使用该配置，等你确认它提议得靠谱了，再换成 auto 让它跳过审批、自己直接写。reviewMode: "hybrid" —— 用哪种方式去发现可沉淀的内容，一共四挡：off 完全不自动找，heuristic 只靠关键词扫描，llm 让模型回看一遍整段对话，hybrid 则是前两者一起上。新手用 hybrid 就行，几挡的差别后面讲。因为还在实验阶段，它的内部行为各版本之间可能会变，所以第一次用务必先 pending 模式，亲眼看几轮它都想存些什么，别一上来就让它自动写文件。这几个参数取不同的值，能搭出好几种不同的组合，对应不同的使用场景：// 保守：只接受模型显式调工具，关闭一切自动捕获 { autoCapture: false, approvalPolicy: "pending", reviewMode: "off" } // 评审优先（推荐）：自动捕获，但都先排队等审批 { autoCapture: true, approvalPolicy: "pending", reviewMode: "hybrid" } // 受信自动化：本地工作区里安全提案自动写盘 { autoCapture: true, approvalPolicy: "auto", reviewMode: "hybrid" } // 省钱：不跑回看模型，只认显式纠正短语 { autoCapture: true, approvalPolicy: "pending", reviewMode: "heuristic" }除了上面这四个，还有几个调阈值、限大小的配置项，第一次上手用不到，附在这里备查：键默认范围作用reviewInterval151..200累计多少轮成功对话后跑一次回看模型reviewMinToolCalls81..500累计多少次工具调用后跑回看模型reviewTimeoutMs450005000..180000回看模型单次运行的超时maxPending501..200每工作区最多保留多少提案maxSkillBytes400001024..200000生成的 skill / 支持文件单文件大小上限实战 Skill Workshop插件启用之后，这一节我们手动走一遍完整流程，亲眼看它怎么把一句话变成一份 skill。整个过程就三件事：在对话里说一句带「纠正口吻」的话、看看提案有没有进队列、再把它 apply 落地。说一句带「纠正口吻」的话第一步，造一段能被插件捕到的对话。插件会盯着你消息里有没有 next time / always / from now on 这类纠正口吻（完整短语列表后面会讲），命中了就尝试把它沉淀成 skill。注意这些短语得出现在用户消息里，小龙虾回复里出现不算。比如：next time we wo…

带小龙虾逛 ClawHub：自定义 Skill 实战
type: item
date: 2026-06-12T00:32:00+00:00
description: 上一篇我们把 Skills 系统的基本盘讲完了：一份 SKILL.md 就是一份操作手册，仓库自带 53 个 skill，加上内置插件捎来的 14 个，开箱就有 67 个能用；加载时按优先级合并、按声明的依赖条件筛选，再把每个 skill 的「名字 + 描述 + 路径」拼成一段索引塞进系统提示，正文则由模型在命中后自己用 Read 工具按需加载。但这 67 个终究是官方给的，对每个人的工作流来说都谈不上贴身。真正让 OpenClaw 生态贴近场景的，是自定义 skill，既包括别人写好放到第三方市场 ClawHub 上的，也包括你自己写出来的。今天我们就先带小龙虾去 ClawHub 上逛逛，挑一个 skill 装到本地试试；然后自己动手写一个最小可用的 sysinfo skill；最后把它发布回 ClawHub，让别人也能装来用。ClawHub 是什么ClawHub 是 OpenClaw 官方的 skill 和 plugin 注册中心，站点是 clawhub.ai。它在 OpenClaw 生态里的位置，类似 npm 之于 Node、PyPI 之于 Python：一个能公开浏览、版本化、可搜索的技能仓库。官方文档把 ClawHub 提供的能力归成下面这张特性表：特性说明公开浏览skill 目录和 SKILL.md 全文都可以匿名公开查看语义搜索走 embedding 向量匹配，而不是只对关键词版本管理用语义化版本号管理，每次发布生成一个新版本，配带变更说明和标签下载分发每个版本一份 zip 包社区反馈支持收藏（star）和评论安全扫描详情页展示 SkillSpector 和 VirusTotal 安全扫描结果，安装前一眼可见作者修复面板被扫描扣留的版本，作者能在 /dashboard 看到并申诉复扫复扫请求误判时作者可以申请有限次数的复扫人工审核申请审核和审计流程CLI 友好的 API适合自动化和脚本调用装一个 skill 试试ClawHub 上的 skill 怎么装到本地呢？官方提供了两套 CLI 命令行工具，我们可以使用 CLI 手动搜索和安装，也可以直接和小龙虾对话，让它给你安装。两个 CLI 工具ClawHub 有两套官方 CLI 入口：openclaw skills：OpenClaw 自家命令族里的一个子命令，装 OpenClaw 时一并就有。负责搜索、安装、更新 ClawHub 上的 skill，外加查看本地 skill 状态（list / info / check）。clawhub：独立的 CLI 工具，靠 npm i -g clawhub 单独安装，覆盖 ClawHub 全部功能，除了上面那些不用登录就能做的搜索、安装类操作，还包括登录、发布、删除、复扫、同步等需要鉴权的写操作。两者常用命令对照如下：操作openclaw skillsclawhub备注搜索openclaw skills search "<query>"clawhub search "<query>"走向量语义搜索，不只匹配关键词安装openclaw skills install <slug>clawhub install <slug>默认装 latest 标签的版本，拉到 workspace 的 skills/ 下安装指定版本openclaw skills install <slug> --version <ver>clawhub install <slug> --version <ver>想锁定某个旧版本时用更新openclaw skills update <slug>clawhub update <slug>按 .clawhub/lock.json 里的来源元数据重新拉新版本全部更新openclaw skills update --allclawhub update --all把 lock.json 里登记过的 ClawHub skill 一起升级查看列表openclaw skills listclawhub listopenclaw skills list 列本地全部 skill（bundled / managed / workspace）；clawhub list 只列从 ClawHub 装下来的查看详情openclaw skills info <slug>—看本地 skill 的来源、frontmatter 元数据、当前是否可用验证openclaw skills check—把本地所有 skill 的依赖条件挨个核对，不可用的列出来排障登录—clawhub login默认走浏览器授权；也可以 --token <token> 直接贴 API token发布—clawhub skill publish <path>把本地 skill 目录推…

给小龙虾写本操作手册：Skills 系统
type: item
date: 2026-06-08T07:20:00+00:00
description: 前面几篇我们把 OpenClaw 的工具挨个过了一遍，但工具终究只是零件。想象一下你跟小龙虾说一句「把 openclaw 仓库里带 bug 标签的 issue 都过一遍，能修的就开个 PR 修掉」，它得先用 exec 跑 gh issue list 拉 issue 列表，按 label 筛出要动的几个，再用 sessions_spawn 分头派子 agent 去改代码，修完再用 gh pr create 开 PR，最后还得盯着 review 评论看要不要再改一轮。这一串环节该按什么顺序走、被 rate-limit 怎么回退、PR 评审打回来怎么继续，光靠模型每次临场发挥并不稳。它需要一份写好的操作手册。这正是 Skill 这套机制要解决的问题。围绕 Skills 系统我们打算分三篇讲完：今天这一篇先把基本盘讲清楚，什么是 skill、从哪儿加载、如何参与对话；下一篇带大家逛一逛 ClawHub 市场，再动手写一个自己的 skill 并发布上去；第三篇看一个实验性玩法 Skill Workshop，让 agent 把自己干活时学到的流程自动写成 workspace skill。今天先从最基础的开始。什么是 Skill根据官方文档，OpenClaw 的 skill 用的是 Agent Skills 规范：每个 skill 就是磁盘上的一个目录，目录里至少有一份 SKILL.md，文件头是一段 YAML frontmatter，正文是 Markdown 写的操作说明。说白了，Skill 就是给 agent 备的一份操作手册：什么时候该触发、要调哪些底层工具、按什么步骤走、出错了怎么办。它和直接把这些步骤塞进系统提示词最大的区别是按需加载。OpenClaw 只在 agent 真有可能用到时才把它的存在告诉模型，平时不占用上下文；模型觉得用户问题和哪个 skill 相关了，再去把手册正文读出来。最小可用的 SKILL.md 长这样：--- name: hello-world description: A simple skill that says hello. --- # Hello World Skill When the user asks for a greeting, use the `echo` tool to say "Hello from your custom skill!".frontmatter 里 name 和 description 是必填的，如果缺一个，这份 skill 直接被丢掉。其中 description 是给模型看的一句话说明，模型靠它判断当前对话要不要使用这个 skill；正文则是给 agent 看的执行手册。除了 name 和 description 这两个必填字段，OpenClaw 还在 frontmatter 里加了不少自家扩展：声明依赖二进制 / 环境变量 / 配置项的 requires.bins / env / config、控制触发方式的 user-invocable / disable-model-invocation / command-dispatch、限定平台的 os、给桌面端 UI 用的 install 安装提示，以及配合密钥注入的 primaryEnv 等等。具体含义留到后面再细看。三类来源OpenClaw 把 skill 按来源分了好几类，信任级别和生命周期各不相同。优先级从高到低依次是：优先级来源路径1Workspace skills<workspace>/skills2Project agent skills<workspace>/.agents/skills3Personal agent skills~/.agents/skills4Managed / 本地 skills~/.openclaw/skills5Bundled skills随安装包发出6额外目录skills.load.extraDirs（配置里指定）如果几个来源里有同名 skill，高优先级覆盖低优先级。这套规则保证了用户工作区里自己定制的版本永远盖得住官方版本，方便做覆写和打补丁。中间那两层 .agents/skills 是给项目级或个人级的 agent profile 用的，平时不太会动，一般我们只关心三类：bundled：仓库自带，随安装包发出managed：~/.openclaw/skills，从 ClawHub 装下来的或本地打的补丁workspace：<workspace>/skills，用户自己写的定制版本除此之外，插件也可以自带 skill：在 openclaw.plugin.json 里声明一个 skills 目录，插件启用时这些 skill 就会按和 extraDirs 同等的最低优先…

让小龙虾给 Claude Code 派活：学习 OpenClaw 的 ACP 工具
type: item
date: 2026-06-05T00:44:00+00:00
description: 用了好几篇把 OpenClaw 的内置工具箱挨个过完，又花了两篇专门讲浏览器，小龙虾「自己能干哪些活」到这里基本就讲齐了。今天换个方向，看它的另一项能力：收到任务后，它不亲自处理，而是把整个任务交给一个真正的外部编码 agent 去完成。这事其实和之前「让小龙虾分身」那篇里讲的 sub-agents 有点像。当时我们看过，主 agent 跑到一半可以用 sessions_spawn 把子任务派给后台的子 agent。但那些子 agent 都是 OpenClaw 自己的 agent，同一套运行时、同一批工具、同一份 system prompt，本质上还是小龙虾在跟自己的分身协作。ACP 要解决的是另一种诉求：派出去的不是 OpenClaw 子 agent，而是 Claude Code、Gemini CLI、Cursor、Codex 这些外部编码 harness。你在飞书上给小龙虾发一句「把这个仓库里的调试日志清理一下」，它接到之后并不自己写代码，而是在网关那台机器上拉起一个真正的 Claude Code 进程，让 Claude Code 去改文件、跑命令，干完再把结果播报回飞书。OpenClaw 的文档和命令里把这些外部 agent 统称为 harness，本文后面也沿用这个叫法。标准的 ACP 协议动手之前，先花点篇幅认识一下 ACP 这套协议本身，它并不是 OpenClaw 自创的东西。ACP 是 Agent Client Protocol（Agent 客户端协议） 的缩写，由开发 Zed 编辑器的团队提出并开源（Apache 许可），项目主页在 agentclientprotocol.com。它要解决的是一个典型的 N×M 问题：一边是越来越多的编辑器（Zed、JetBrains 系、各种 CLI），另一边是越来越多的 AI 编码 agent（Claude Code、Gemini CLI、Codex 等）。如果每个编辑器都要为每个 agent 单独写一套对接，每个 agent 又得反过来适配每个编辑器的私有接口，组合数量很快就会失控，用户也被绑死在某个特定的「编辑器 + agent」组合上。熟悉 LSP（Language Server Protocol，语言服务器协议） 的同学对这个套路应该不陌生。当年 LSP 用一套标准协议把「编辑器」和「语言能力」解耦，任何编辑器配任何语言服务器都能用；ACP 想做的是同一件事，只是解耦的两端换成了「编辑器」和「编码 agent」。协议本身定义了两个角色：Client：通常是编辑器，或者任何想接入 agent 的宿主程序，它掌握着工作区、权限和界面。Agent：真正干编码活的 AI 工具，比如 Claude Code。技术实现上，ACP 走的是 JSON-RPC 2.0 over stdio：Client 把 Agent 作为一个子进程拉起来，双方通过标准输入输出收发 JSON-RPC 消息。一次典型的交互大致是：Client 先 initialize 握手，再用 session/new 开一个会话，然后把用户的指令通过 session/prompt 发过去；Agent 干活的过程中，靠 JSON-RPC 的通知把进展实时流式推回 Client，需要读写文件或执行命令时，则用反向请求回头向 Client 申请权限。整个过程中，掌权的始终是 Client：权限给不给、界面怎么渲染、能碰工作区里的哪些东西，都由 Client 说了算，Agent 只能请求、不能擅自越界。把上面这条交互链画成时序图，大致是这样：如果你了解 MCP（Model Context Protocol，模型上下文协议），会觉得上面这套流程很眼熟：同样是 JSON-RPC 2.0、同样把对方作为子进程通过 stdio 拉起来、同样有 initialize 握手和流式通知。这并不是巧合，ACP 在设计上就刻意向 MCP 看齐、复用它的传输模型。两者的区别在于解决的问题不同：MCP 标准化的是「agent 如何连上工具和数据源」，这时 agent 是 client，MCP server 提供工具；ACP 标准化的是「编辑器或宿主如何连上一个 agent」，这时编辑器是 client，agent 负责编码。它们还能叠在一起用：使用 session/new 开新会话时，可以顺带把要连的 MCP server 一起声明掉，让被拉起来的 ACP Agent 自己再去当一回 MCP Client，连上它需要的工具。讲清楚标准协议，再看 OpenClaw 的位置就顺理成章了：在 ACP 这套协议里，OpenClaw 扮演的是 Client 角色。平时 Zed、JetBrains 是用 ACP 在编辑器里接入 Claude Code，Op…

给小龙虾配个浏览器：学习 browser 工具（二）
type: item
date: 2026-06-01T00:36:00+00:00
description: 上一篇我们把 browser 工具的运行环境从头捋了一遍：首先学习了它的参数定义，然后使用 host / sandbox / node 确定浏览器运行在哪里，以及使用 profile 确定运行哪个浏览器。环境备齐，这一篇书接上文，来看 agent 怎么驱动浏览器在页面上点点点，以及这套动作背后的原理。标签页管理browser 一次只在一个标签页上干活，所以为了保证后续的动作没问题，我们首先要认准操作哪个标签页。我们可以使用 openclaw browser <动作> 命令行来管理它，开新标签页用 open，网址作参数，再用 --label 顺手贴个标签：$ openclaw browser open https://www.aneasystone.com --label blog opened: https://www.aneasystone.com/ tab: t1 label: blog id: 7E73979D64CBF48058818D78D50FB94B该命令返回了几个参数：tab 是形如 t1 的稳定 tabId，label 是我们自己起的 blog 标签，id 则是 CDP 协议的原始 targetId。这几个参数都可以作为后续操作这个标签页时的标识。比如之后想再操作它，用 focus 把它切到前台就行，tabId、label、targetId 这三种都认，下面三条指向的都是同一个页面：$ openclaw browser focus t1 # 用 tabId $ openclaw browser focus blog # 用 label $ openclaw browser focus 7E73979D # 用 targetId（前缀也认）顺带分清一个常被搞混的点：open 每次都新开一个标签页，而 navigate 是让当前标签页原地跳到另一个网址，所以常见用法是先 focus 选中、再 navigate 到某个网址；也可以加上可选的 --target-id 直接跳转：$ openclaw browser navigate https://www.aneasystone.com/about-me.html --target-id blog再开一个不带 label 的，然后用 tabs 列出所有的标签页：$ openclaw browser open https://www.baidu.com opened: https://www.baidu.com/ tab: t2 id: 9B0D7E3F1A2C4856E7F0A1B2C3D4E5F6 $ openclaw browser tabs 1. aneasystone's blog [t1 label:blog] https://www.aneasystone.com/ id: 7E73979D64CBF48058818D78D50FB94B 2. 百度一下，你就知道 [t2] https://www.baidu.com/ id: 9B0D7E3F1A2C4856E7F0A1B2C3D4E5F6我们还可以加一个 --json 参数，看到完整的数据结构：$ openclaw browser --json tabs { "tabs": [ { "targetId": "7E73979D64CBF48058818D78D50FB94B", "title": "aneasystone's blog", "url": "https://www.aneasystone.com/", "wsUrl": "ws://127.0.0.1:18800/devtools/page/7E73979D64CBF48058818D78D50FB94B", "type": "page", "suggestedTargetId": "blog", "tabId": "t1", "label": "blog" } ] }收尾要关掉标签页就用 close 参数：$ openclaw browser close blog closed tab如果忘记关也没有关系，OpenClaw 给主 agent 的浏览器会话配了一套自动清理机制：空闲超过一定时间（默认 120 分钟）的标签页会被回收，每个会话还有个标签页数量上限（默认 8 个），后台每隔几分钟扫一遍；子 agent、cron、ACP 这类任务跑完时，也会顺手把自己开的标签页关掉，不至于在后台留一堆的孤儿窗口。SSRF 防护刚才用 open 和 navigate 打开网址，看着什么链接都能打开，其实不然。之前学习工具箱时就提到过，web_fetch 自带一套 SSRF 防护：凡是能由外部输入决定去访问哪个地址的工具，都得防着被人诱导去访问内网。浏…