Claude Code 是如何狙杀中国用户的

继昨天 Claude Code 源码泄露之后,全网已经狂欢了一整天。 除了学习它的 agent 架构、prompt 组织方式,对很多又爱又恨 Claude Code 的中国用户来说,还可以研究下怎么"道高一尺、魔高一丈!" 我们来对比泄露的代码和 @anthropic-ai/claude-code 当前 npm 包里的 cli.js,研究下 Claude Code 是如何精准定位用户的! 一、Claude Code 到底在送什么 1. system prompt 里就带着环境信息 很多人会本能地觉得,“追踪"无非就是打点埋点,把那一路关了就行。 ...

2026-04-01 · 2 min · Kada Liao

泄露的 Claude Code 源代码解析

今天有人在 npm registry 上发现,Anthropic 发布的 Claude Code 包里附带了 .map 文件——source map。 这东西本来是给浏览器 debug 用的,附带进生产包是个失误。代价是:原始 TypeScript 源码可以被完整还原出来。 一时间社区里传开了,完整的源代码结构被扒了出来。 本着学习 coding agent 工程实现的目的,深入读一下这份代码,看看 Anthropic 是怎么构建一个生产级 coding agent 的。 先看项目结构 整个代码库遵循层级优先的组织方式——顶层目录对应架构子系统,不是按功能分组的。 kadaliao/claude-code (main 分支) ├── main.tsx # CLI 入口 ├── QueryEngine.ts # 核心查询编排引擎 ├── Tool.ts # 工具接口定义与类型 ├── Task.ts # 后台任务类型定义 ├── query.ts # 异步生成器查询循环 ├── tools.ts # 工具注册表 (40+ 内置工具) ├── commands.ts # 斜杠命令注册表 (80+ 命令) ├── context.ts # 系统/用户上下文组装 ├── ink.ts # 自定义 Ink 渲染引擎封装 │ ├── screens/ │ ├── REPL.tsx # 主交互式 REPL(约 5000 行) │ ├── Doctor.tsx # 诊断界面 │ └── ResumeConversation.tsx # 会话恢复界面 │ ├── components/ # 100+ React UI 组件 ├── hooks/ # 自定义 React Hooks ├── state/ # AppState 状态存储 ├── services/ # 后端服务模块 ├── tools/ # 各工具的具体实现 ├── commands/ # 各斜杠命令的实现 ├── skills/ # 动态技能加载系统 ├── tasks/ # 后台 Agent 任务类型 ├── entrypoints/ # 初始化与 SDK 入口 ├── bridge/ # 远程连接与移动端桥接 ├── coordinator/ # 协调器模式编排 ├── assistant/ # 助手(KAIROS)模式 ├── plugins/ # 插件系统 └── voice/ # 语音输入集成 几个马上能引起注意的点: ...

2026-03-31 · 3 min · Kada Liao

Human 真的需要 in the loop 吗?

这个问题听起来简单,但答案可能比你想象的更微妙。 很多人讨论 AI 的时候,喜欢问这么一句: “Human 真的需要 in the loop 吗?” 这个问题听起来很自然,但我越来越觉得,它问得还是有点太粗了。 真正值得问的,不是"要不要人",而是: 人在什么层级、什么条件、以什么成本,留在什么样的 loop 里。 因为 AI 时代真正发生的变化,不是人突然消失了,而是—— 人的位置上移了。 很多低层、重复性的执行 loop,确实正在被系统接管。但在更高层的目标定义、结果验收、例外处理、责任承担这些环节,人不但没有退出,反而变得更关键。 所以更准确的问题其实是:未来什么样的人,还能留在高价值的 loop 里。 01 / 不是要不要人,而是人留在哪一层 过去很多人理解自动化,总是带着一种二元视角: 要么人亲自做 要么系统自动做 但真实世界并不是这么切的。 同一个系统里,本来就存在很多层不同的 loop。 低层的 loop,比如调接口、改格式、填表单、搬运数据、执行规则明确的操作——这些最容易被替代。它们目标清楚,输入输出稳定,出错后反馈也容易获得。 高层的 loop,就完全不同了: 目标到底是什么? 什么叫"足够好"? 出现例外时该不该破例? 多个目标冲突时先保哪个? 一旦出事,谁来承担责任? 这些问题,很难被一句"让 AI 自己做完闭环"带过去。 所以今天真正的变化,不是 human out of the loop,而是: Human moves up the loop。 人正在从"亲手做每一步",慢慢转向: 定义目标 设置边界 看异常 做验收 接管例外 为结果负责 这才是更准确的现实。 02 / 为什么现阶段人还退不干净 很多人会说,如果需求、方向、标准都定义清楚了,剩下的不就是执行吗?那当然应该尽量 out of the loop。 ...

2026-03-30 · 2 min · Kada Liao

Harness Engineering 是什么,如何落地

最近将 Token 翻译成词元之后,又有个词突然变热且暂时没有合适的翻译:Harness Engineering。 不是新模型,不是新框架,甚至不是新工具——就是这么一个词,突然开始出现在各种技术讨论里。 它从哪来的?它在说什么?为什么现在火起来? 我们来拆解三篇相关的重要文章,最后讲讲实践中如何落地。 先说这个词本身 Harness,字面意思是"缰绳"或"挽具"。 放进 AI 工程的语境:Harness 是把 agent 纳入工程系统的那套控制结构——让 agent 的工作变得可约束、可验证、可回放,而不是每次运气好就成功、运气不好就不知道哪里出了问题。 它不是某一个工具,也不是某一个 prompt 技巧。它是一套工程思路:当代码主要由 agent 生成,工程师的工作重心从"写代码"转向"设计让 agent 能够有效工作的环境"。 听起来很虚?下面看具体的。 第一篇:OpenAI,2 月 11 日 这个词真正开始传播,是因为 OpenAI 在 2 月 11 日发了一篇工程博客,标题叫《Harness Engineering: Leveraging Codex in an Agent-First World》。 文章里有一组数字,很多人看完沉默了: 5 个月。3 名工程师。约 100 万行代码。约 1,500 个 PR,平均每人每天 3.5 个。 更关键的是:从第一个 commit 开始,仓库里没有一行代码是人手写的。 连最初的 AGENTS.md——用来告诉 agent 怎么在这个项目里工作的文件——都是 agent 自己写的。 但这不是 vibe coding。 OpenAI 团队在文章里说了一句很关键的话: 早期进展比预期慢——不是因为 Codex 没有能力,而是因为环境没有定义好。 ...

2026-03-27 · 3 min · Kada Liao

微信官方小龙虾插件协议拆解

今天微信发布了官方龙虾插件 微信ClawBot,支持 OpenClaw 接入个人微信。 第一时间尝试之后,深入源代码,分析其协议实现。废话不多说,直接开始。 一张图先看全局 整套协议的结构其实非常规整:控制面走 Bot 网关,��据面走 CDN,插件本地负责把两者接起来。 控制面接口主要有 5 个: getupdates sendmessage getuploadurl getconfig sendtyping 数据面则只有一件事:媒体文件通过 CDN 上传/下载,本地做 AES-128-ECB 加解密。 这就是理解整套协议的入口。 1. 登录链路:二维码授权 -> bot_token 先看登录。 插件登录阶段只依赖两个接口: GET /ilink/bot/get_bot_qrcode?bot_type=3 GET /ilink/bot/get_qrcode_status?qrcode=... 第一步拿二维码,第二步轮询二维码状态。它的状态机大致如下: 确认授权后,服务端会返回: { "status": "confirmed", "bot_token": "<bot_token>", "ilink_bot_id": "xxxxxxxx@im.bot", "ilink_user_id": "xxxxxxxx@im.wechat", "baseurl": "https://ilinkai.weixin.qq.com" } 这里真正关键的是 4 个字段: bot_token:后续所有业务请求的 Bearer 凭证 ilink_bot_id:Bot 身份 ilink_user_id:绑定的微信用户 baseurl:服务端下发的 API 基址 插件会把这些值保存到本地账号文件,后续 gateway 启动时直接复用。 一句话概括登录阶段:二维码只是授权入口,真正建立会话能力的是 bot_token。 2. 统一请求头:协议外壳非常稳定 除了扫码登录用 GET 之外,后续业务接口基本都是 JSON POST。统一请求头如下: Content-Type: application/json AuthorizationType: ilink_bot_token Authorization: Bearer <bot_token> X-WECHAT-UIN: <base64(random uint32 decimal string)> SKRouteTag: <optional> 这里可以顺手记住 3 个实现细节。 ...

2026-03-22 · 4 min · Kada Liao

小龙虾为何变蠢、失忆?深入理解 OpenClaw 记忆系统

很多人辛辛苦苦养的小龙虾,都会在某个时刻让主人产生一种挫败感: 昨天刚说过的偏好,今天新开一个会话又得重讲; 明明说了"记住这个",过几天再问,它像没听过; 会话越长,助手越像"注意力涣散",前面已经确认过的事,后面又绕回来。 这类问题看起来像"小龙虾变笨了",怀疑给虾使用的大模型不够智能?其实大多数时候都不是模型能力问题,而是记忆机制没有被正确使用。 (本文所说的小龙虾,通过 OpenClaw 部署。研究对象也是 OpenClaw。接下来将统一口径,使用 OpenClaw 来称呼。) OpenClaw 的记忆系统并不神秘。恰恰相反,它非常工程化:记忆不是藏在黑盒里的,而是落在工作区里的 Markdown 文件、会话历史和检索索引里。 你理解了这套机制,很多"为什么它会忘"的问题都会立刻变清楚。 这篇文章在深入研究 OpenClaw 官方文档、拆解源代码之后,讲清楚三件事: OpenClaw 到底靠什么"记住"你; 为什么它会忘; 怎样把它调到一个长期稳定、可维护的状态。 一、先把一个认知纠正过来:模型本身不会跨会话记忆 大语言模型并不会像人一样,把昨天的对话自然带到今天。 它每次能看到的,只有本轮请求被送进上下文窗口的内容。如果某条信息没有进入当前上下文,模型就等于没看见;如果某条信息从来没有被写到磁盘,下次重启后也就无从谈起。 这也是 OpenClaw 记忆设计最核心的一条原则: 文件才是记忆的唯一可信来源。 按照 OpenClaw 当前官方文档,记忆是工作区中的纯 Markdown 文件:模型只会"记住"那些被写入磁盘、并在合适时机重新加载或检索出来的内容。 这件事听上去朴素,但它带来一个非常重要的推论: “我已经在对话里说过了”,不等于"它下次还会记得"; “它刚才答应会记住”,不等于"它已经落盘"; “它知道我是什么意思”,不等于"这条信息已经进入长期记忆层"。 如果你把 OpenClaw 当作一个有天然长期记忆的大脑,就很容易失望;如果你把它当作一个带文件系统、检索和压缩机制的 Agent 运行时,它的行为反而会变得非常可预测。 二、从使用体验看,OpenClaw 实际上有三层"记忆" 官方文档在"记忆文件"这一层主要讲两种载体:MEMORY.md 和 memory/YYYY-MM-DD.md。但从实际使用体验看,OpenClaw 的"记得住"与"记不住",是三层机制共同作用的结果: 层级 载体 作用 典型内容 工作记忆 当前会话历史 维持本轮任务的连续性 刚才的问答、工具调用结果、当前任务状态 短期记忆 memory/ 保留近期上下文和运行日志 会话摘要、日常运行记录、短期上下文 长期记忆 MEMORY.md 保留跨会话稳定信息 偏好、长期事实、重要决策、环境约定 这三层各自解决的是不同问题。 1. 工作记忆:负责"眼前这件事" 当前会话历史就是工作记忆。它保证 OpenClaw 在同一段对话里还能接着前文往下走。 ...

2026-03-18 · 4 min · Kada Liao

MCP 协议入门:给 AI Agent 装上标准化工具接口

2024 年底,Anthropic 开源了 MCP(Model Context Protocol),一个让 AI 模型与外部工具、数据源交互的标准化协议。2025 年以来,越来越多的工具和平台开始支持 MCP。 MCP 解决了什么问题 在 MCP 之前,每个 AI 应用都要自己实现工具集成: 应用 A → 自己写 Google Drive 集成 应用 B → 自己写 Google Drive 集成 应用 C → 自己写 Google Drive 集成 有了 MCP: Google Drive MCP Server(写一次) ↓ 应用 A、B、C 都能直接接 MCP 是一个客户端-服务端协议,AI 应用是客户端,工具提供方实现 MCP Server。 核心概念 MCP Server 可以暴露三种东西: Tools:模型可以调用的函数(类似 Function Calling) Resources:模型可以读取的数据(文件、数据库等) Prompts:预定义的 Prompt 模板 用 Python 写一个最简 MCP Server from mcp.server import Server from mcp.server.stdio import stdio_server from mcp import types app = Server("my-tools") @app.list_tools() async def list_tools() -> list[types.Tool]: return [ types.Tool( name="get_time", description="获取当前时间", inputSchema={ "type": "object", "properties": { "timezone": { "type": "string", "description": "时区,如 Asia/Shanghai" } } } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_time": from datetime import datetime import pytz tz = pytz.timezone(arguments.get("timezone", "Asia/Shanghai")) now = datetime.now(tz) return [types.TextContent(type="text", text=now.strftime("%Y-%m-%d %H:%M:%S %Z"))] async def main(): async with stdio_server() as streams: await app.run(*streams, app.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main()) 在 Claude Desktop 里使用 在 ~/Library/Application Support/Claude/claude_desktop_config.json 里配置: ...

2025-04-15 · 2 min · Kada Liao

LangGraph 实战:构建一个可控的 ReAct Agent

ReAct(Reasoning + Acting)是让 Agent 先思考再行动的范式。这篇文章用 LangGraph 实现一个完整的 ReAct Agent,并加入一些生产环境需要的工程细节。 ReAct 的执行流程 用户输入 ↓ 思考(Thought):分析问题,决定下一步 ↓ 行动(Action):选择并调用工具 ↓ 观察(Observation):获取工具结果 ↓ 循环,直到能给出最终答案 ↓ 最终答案 用 LangGraph 实现 from typing import TypedDict, Annotated from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, END import operator class AgentState(TypedDict): messages: Annotated[list[BaseMessage], operator.add] iteration: int # 防止无限循环 # 初始化 LLM(绑定工具) llm = ChatOpenAI(model="gpt-4o", temperature=0) llm_with_tools = llm.bind_tools(tools) def should_continue(state: AgentState) -> str: """决定下一步走哪条边""" last_message = state["messages"][-1] # 超出最大迭代次数,强制结束 if state["iteration"] >= 10: return "end" # 最后一条消息没有工具调用,说明已经有最终答案了 if not hasattr(last_message, "tool_calls") or not last_message.tool_calls: return "end" return "tools" def call_llm(state: AgentState) -> AgentState: """调用 LLM 节点""" response = llm_with_tools.invoke(state["messages"]) return { "messages": [response], "iteration": state["iteration"] + 1 } def call_tools(state: AgentState) -> AgentState: """执行工具调用节点""" last_message = state["messages"][-1] tool_messages = [] for tool_call in last_message.tool_calls: tool_name = tool_call["name"] tool_args = tool_call["args"] try: result = tool_registry[tool_name].invoke(tool_args) content = str(result) except Exception as e: content = f"工具调用失败:{str(e)}" # 错误不崩溃,反馈给模型 tool_messages.append(ToolMessage( content=content, tool_call_id=tool_call["id"] )) return {"messages": tool_messages} # 构建图 graph = StateGraph(AgentState) graph.add_node("llm", call_llm) graph.add_node("tools", call_tools) graph.set_entry_point("llm") graph.add_conditional_edges("llm", should_continue, {"tools": "tools", "end": END}) graph.add_edge("tools", "llm") # 工具执行完,回到 LLM 节点 agent = graph.compile() 加入 Human-in-the-loop LangGraph 支持在某些步骤暂停,等待人工确认: ...

2024-11-18 · 2 min · Kada Liao

Agent 框架横评:LangGraph vs AutoGen vs CrewAI

2024 年,Agent 框架的竞争进入白热化阶段。我在几个项目里分别用过 LangGraph、AutoGen 和 CrewAI,这篇文章做一个工程视角的横评。 三者定位 框架 核心定位 适合场景 LangGraph 有状态的图执行引擎 需要精确控制流程的复杂 Agent AutoGen Multi-Agent 对话框架 多个 Agent 协作解决问题 CrewAI 角色扮演式 Multi-Agent 任务分工明确的团队协作场景 LangGraph LangGraph 是 LangChain 团队推出的,把 Agent 的执行流程建模成一个有向图。 from langgraph.graph import StateGraph, END from typing import TypedDict class AgentState(TypedDict): messages: list next_step: str def agent_node(state: AgentState): # 调用 LLM 决策下一步 ... return {"next_step": "tool" if needs_tool else "end"} def tool_node(state: AgentState): # 执行工具 ... return {"messages": [...]} graph = StateGraph(AgentState) graph.add_node("agent", agent_node) graph.add_node("tool", tool_node) graph.add_edge("tool", "agent") graph.add_conditional_edges("agent", lambda s: s["next_step"]) 优点:流程完全可控,支持循环、分支、人工介入(human-in-the-loop),适合生产环境。 缺点:上手成本高,要理解图的概念;代码相对冗长。 AutoGen AutoGen 是微软出的,核心是让多个 Agent 通过对话协作: from autogen import AssistantAgent, UserProxyAgent assistant = AssistantAgent( name="助手", llm_config={"model": "gpt-4o"} ) user_proxy = UserProxyAgent( name="用户", human_input_mode="NEVER", # 全自动 code_execution_config={"work_dir": "workspace"} ) user_proxy.initiate_chat( assistant, message="帮我写一个爬虫,抓取 Hacker News 首页" ) 优点:能自动执行代码,适合需要写代码解决问题的场景。 ...

2024-09-10 · 1 min · Kada Liao

RAG 进阶优化:提升检索质量的七个方向

上一篇文章介绍了基础 RAG 的搭建。基础 RAG 跑起来之后,你会发现效果差强人意——召回的内容不够准、回答有时候答非所问。这篇文章梳理提升 RAG 效果的常见优化方向。 方向 1:切块策略优化 基础的固定大小切块太粗糙,几个更好的策略: 按语义切块(Semantic Chunking): from langchain_experimental.text_splitter import SemanticChunker from langchain_openai import OpenAIEmbeddings splitter = SemanticChunker( OpenAIEmbeddings(), breakpoint_threshold_type="percentile" ) chunks = splitter.split_text(document) 语义切块基于 Embedding 相似度判断段落边界,比按字符数切更合理。 父子文档(Parent-Child):小块用于检索,大块用于生成: from langchain.retrievers import ParentDocumentRetriever retriever = ParentDocumentRetriever( vectorstore=vectorstore, docstore=InMemoryStore(), child_splitter=RecursiveCharacterTextSplitter(chunk_size=200), parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000), ) 小块召回精准,但上下文不足;大块提供足够上下文,但噪声多。父子文档两全其美。 方向 2:Query 改写 用户的提问往往不是最优的检索 query: async def rewrite_query(query: str) -> list[str]: prompt = f""" 生成 3 个不同角度的检索查询,帮助从文档库中找到回答以下问题的信息。 原始问题:{query} 输出格式:每行一个查询 """ response = await llm.ainvoke(prompt) queries = response.content.strip().split("\n") return [query] + queries # 原始查询 + 改写的查询 用多个 query 检索,再合并去重,召回率显著提升。 方向 3:HyDE(假设文档嵌入) 让模型先生成一个"假设的答案文档",用它来检索: async def hyde_retrieve(query: str) -> list[Document]: # 让模型生成一个假设的答案 hypothetical_doc = await llm.ainvoke( f"写一段简短的文章,回答以下问题(即使你不确定):{query}" ) # 用假设答案的向量来检索,而不是用问题的向量 docs = vectorstore.similarity_search(hypothetical_doc.content, k=4) return docs 假设答案的 Embedding 比问题的 Embedding 更接近文档的分布,检索效果往往更好。 ...

2024-05-22 · 2 min · Kada Liao