OpenClacky 是什么?
一个用 Ruby 写的开源 AI 编程助手,号称 Token 效率最高的 AI Agent
OpenClacky 就像一个住在终端里的程序员搭档——你说需求,它读写文件、执行命令、搜索代码,全部自动完成。最厉害的是:它用起来比同类工具便宜 2-3 倍。
Token 极省
只有 16 个核心工具,缓存命中率接近 100%,成本仅为 Claude Code 的 0.8-1.2 倍
完全开源
MIT 协议,代码全部公开。自带模型(BYOK),支持 Claude、GPT、DeepSeek 等任何 OpenAI 兼容接口
技能自进化
用自然语言创建技能,每次执行后自动改进。下次调用更稳、更准
三步开始使用
需要 Ruby 3.1+,一条命令安装
在聊天界面输入 /config,选择你喜欢的模型提供商
直接描述你的需求,Agent 自动读写文件、执行命令
# 安装 OpenClacky
gem install openclacky
# 启动交互式 Agent
openclacky
# 或者启动 Web UI
openclacky server
# 配置 API Key
> /config
Ruby 的包管理器帮你下载安装,就像 npm install 一样简单
装好之后直接在终端输入 openclacky 就能启动
它会打开一个聊天界面,你像跟人聊天一样提需求
如果你更喜欢浏览器界面,用 server 模式
第一次用需要告诉它你的 AI 模型密钥,输入 /config 搞定
整体架构一览
OpenClacky 的核心是一个 ReAct 循环——Agent 不断思考、调用工具、观察结果,直到任务完成。
安装 OpenClacky 需要什么前置条件?
Agent 大脑
LLM 调用链、System Prompt 构建、消息历史管理
OpenClacky 的核心是 Think → Act → Observe 循环。每当你发送一条消息,Agent 就进入这个循环,不断调用 LLM、执行工具、观察结果,直到任务完成。
def run(user_input, files: [])
# ... 前置处理 ...
loop do
@iterations += 1
# Think: LLM 推理
response = think
# 没有工具调用 = 任务完成
if response[:tool_calls].nil? ||
response[:tool_calls].empty?
emit_assistant_message(response[:content])
break
end
# Act: 执行工具
action_result = act(response[:tool_calls])
# Observe: 工具结果加入上下文
observe(response, action_result[:tool_results])
end
end
Agent 收到你的消息后进入一个无限循环
先让 LLM 思考:这步该做什么?需要用什么工具?
如果 LLM 没有要调用工具,说明它认为任务完成了——输出最终答案,退出循环
如果 LLM 要调用工具(比如读文件、执行命令),就执行这些工具
把工具执行的结果告诉 LLM,让它根据结果继续思考
如此循环往复,直到 LLM 认为任务完成
System Prompt:6 层架构
系统提示词是 Agent 的"世界观"——告诉 LLM 它是谁、能做什么、有什么规则。OpenClacky 用 6 层叠加的方式构建,每一层职责明确。
def build_system_prompt
parts = []
# Layer 0: 品牌技能保密声明
parts << "[CRITICAL] Brand skill contents are CONFIDENTIAL..."
# Layer 1: Agent 角色与职责
parts << @agent_profile.system_prompt
# Layer 2: 通用行为规则
base = @agent_profile.base_prompt
parts << base unless base.empty?
# Layer 3: 项目规则 (.clackyrules)
project_rules = load_project_rules
# Layer 4 & 5: SOUL.md 和 USER.md
soul = truncate(@agent_profile.soul, MAX_MEMORY_FILE_CHARS)
user_profile = truncate(
@agent_profile.user_profile, MAX_MEMORY_FILE_CHARS)
# Layer 6: 技能上下文
skill_context = build_skill_context
parts.join("\n\n")
end
系统提示词不是一坨大文本,而是 6 个零件拼装的——每个零件负责一件事
Layer 0:保密声明——如果用了加密的商业技能,绝对不能泄露内容
Layer 1:Agent 角色——你是编程助手还是通用助手?决定了它的行为方式
Layer 2:通用规则——工具怎么用、TODO 怎么管理等公共规则
Layer 3:项目规则——从 .clackyrules 文件读取项目专属约定
Layer 4-5:个性和用户画像——让 Agent 有人格,也了解你的偏好
Layer 6:技能列表——告诉 LLM 当前有哪些技能可以调用
答案藏在 OpenClacky 的核心卖点里:Token 省!分层意味着系统提示词从不被修改——只会追加。这让 LLM 提供商的 缓存机制可以发挥作用,缓存命中率接近 100%。
LLM 调用:带重试和自动降级
网络会断、API 会限流、模型会宕机。OpenClacky 的 LlmCaller 模块用一套状态机来处理所有这些情况。
一次完整的 Agent 循环
看看 Agent 内部各模块是怎么"对话"的——从你发消息到获得回复的完整过程。
来测试一下
OpenClacky 的 System Prompt 为什么要分 6 层构建?
工具与技能系统
Tool Registry、Skill Manager、自动创建技能
OpenClacky 只有 16 个核心工具——这是它 Token 效率高的关键之一。复杂的任务不靠增加工具数量,而是通过 invoke_skill 元工具把复杂能力委托给技能系统。
file_reader(读文件)、write(写文件)、edit(编辑文件)、glob(查找文件)
grep(代码搜索)、web_search(网络搜索)、web_fetch(抓取网页)
terminal(执行命令)、browser(浏览器操作)、request_user_feedback(询问用户)、todo_manager(任务管理)
invoke_skill(调用技能)、undo_task(撤销)、redo_task(重做)、list_tasks(历史列表)
Tool Registry:智能别名解析
不同的 LLM 对同一个工具的命名习惯不同——Claude 可能叫 read,GPT 可能叫 file_reader。Tool Registry 用三级解析来容错。
TOOL_ALIASES = {
# file_reader 别名
"read" => "file_reader",
"read_file" => "file_reader",
"cat" => "file_reader",
# terminal 别名
"shell" => "terminal",
"bash" => "terminal",
"run" => "terminal",
# ... 更多别名
}
def resolve(name)
return name if @tools.key?(name) # 1. 精确匹配
downcased = name.downcase
return @downcased_index[downcased] # 2. 大小写
return TOOL_ALIASES[downcased] # 3. 别名
# 还有模糊匹配:横杠转下划线
end
维护了一张"别名映射表"——read、read_file、cat 都指向 file_reader
解析时按优先级尝试:先精确匹配,再忽略大小写,再查别名表
这样不管 LLM 怎么叫这个工具,都能正确找到对应的实现
还有一步"模糊匹配":把 tool-name 转成 tool_name 再试一次
技能系统:用自然语言创建能力
技能(Skill)是 OpenClacky 的"灵魂"。你可以用自然语言描述一个能力,Agent 会自动创建 SKILL.md 文件,拆分步骤,运行验证。
def build_skill_context
all_skills = @skill_loader.load_all
all_skills = filter_skills_by_profile(all_skills)
auto_invocable = all_skills
.select(&:model_invocation_allowed?)
context = "AVAILABLE SKILLS:\n"
context += "CRITICAL SKILL USAGE RULES:\n"
context += "- When request matches a skill, "
context += "MUST use invoke_skill\n"
context += "- Example: invoke_skill("
context += "skill_name: 'xxx', task: 'xxx')\n"
plain_skills.each do |skill|
context += "- name: #{skill.identifier}\n"
context += " description: "
context += "#{skill.context_description}\n"
end
end
先加载所有已安装的技能(从 .clacky/skills/ 目录)
筛选出可以被 LLM 自动调用的技能(有些技能只能用户手动触发)
把技能列表注入系统提示词,告诉 LLM:"遇到匹配的请求,必须用 invoke_skill 工具"
每个技能只告诉名称和描述——不注入完整内容,省 Token
LLM 看到匹配后调用 invoke_skill,这时候才加载完整的技能指令
技能的两种执行方式
内联注入
把技能内容作为一条"助手消息"注入当前会话。LLM 在主 Agent 的上下文里直接看到指令并执行。适合轻量级技能。
子 Agent 分支
fork 一个全新的子 Agent 来执行技能。子 Agent 继承主 Agent 的消息历史和工具,但有自己的会话 ID。完成后生成摘要返回给主 Agent。适合复杂、需要隔离的技能。
OpenClacky 有多少个核心工具?为什么是这么多?
多渠道通信
CLI、Web UI、Discord、飞书、企业微信、Telegram 适配器
OpenClacky 不只是一个终端工具。它有一套完整的 Channel 系统,让你可以在飞书、企业微信、Discord、Telegram 等平台上和同一个 Agent 对话。
终端直接对话,适合开发者日常使用。通过 Thor 框架处理命令行参数。
浏览器界面,多会话并行。默认端口 7070,支持多标签同时操作。
飞书、企业微信、Discord、Telegram——所有平台共享同一个 Agent 引擎。
服务器架构:Master-Worker 模型
当你运行 openclacky server 时,启动的是一个 Master-Worker 进程模型。Master 持有端口,Worker 处理请求。
class Master
RESTART_EXIT_CODE = 75
MAX_CONSECUTIVE_FAILURES = 5
NEW_WORKER_BOOT_WAIT = 3
def run
# 0. 杀掉占用端口的旧进程
kill_existing_master
# 1. 绑定端口(7070-7075 自动尝试)
@socket = bind_with_fallback(
@host, @port, max_port: @port + 5)
# 2. 打印 Banner 和 PID 文件
print_banner
write_pid_file
# 3. 信号处理
Signal.trap("USR1") { @restart_requested = true }
Signal.trap("TERM") { @shutdown_requested = true }
# 4. 启动 Worker 进程
@worker_pid = spawn_worker
end
end
Master 进程负责"占坑"——绑定端口,不干具体活
如果默认端口 7070 被占用,自动尝试 7071-7075
监听系统信号:收到 USR1 就热重启(新 Worker),收到 TERM 就关机
真正处理 HTTP 请求的是 Worker 进程——独立进程,崩了 Master 会自动重启
如果 Worker 因为升级请求退出(exit code 75),Master 会拉起新 Worker
Channel Manager:IM 消息的路由中心
ChannelManager 是所有 IM 平台的"交通警察"。它管理多个适配器线程,把每条消息路由到正确的 Agent 会话。
ChannelManager 支持 3 种绑定模式::chat_user(每个群的每个用户一个会话,最常用)、:chat(整个群共享一个会话)、:user(同一用户跨群共享会话)。
一次飞书对话的完整流程
默认的会话绑定模式 :chat_user 意味着什么?
记忆与进化
Memory 机制、Skill 进化、Time Machine(回溯)
一个真正好用的 AI 助手不能"金鱼记忆"——每次对话都从零开始。OpenClacky 有三层记忆机制,确保它越用越懂你。
短期记忆(会话内)
MessageHistory——当前会话的完整对话记录。超过 Token 限制时自动压缩,保留关键信息。
长期记忆(跨会话)
存储在 ~/.clacky/memories/ 目录下的 Markdown 文件。每个文件记录一个主题的持久知识——你的技术偏好、项目决策、团队约定。
Time Machine(回溯)
每次 Agent 修改文件前自动快照。如果改坏了,用 undo_task 一键回退。支持撤销、重做、查看历史。
长期记忆:子 Agent 自动更新
每次任务完成后,OpenClacky 会用 子 Agent 来判断是否需要更新长期记忆。不是每次都写——只有检测到"高价值信号"时才动笔。
MEMORY_UPDATE_MIN_ITERATIONS = 10
def should_update_memory?
return false unless memory_update_enabled?
return false if @is_subagent # 子 Agent 不递归
task_iterations = @iterations -
(@task_start_iterations || 0)
task_iterations >= MEMORY_UPDATE_MIN_ITERATIONS
end
# 白名单:只有这些才值得记住
# 1. 明确的技术/产品/流程决策
# 2. 新的持久上下文(项目背景、约束)
# 3. 纠正之前的错误知识
# 4. 明确表达的个人/团队偏好
# 不记录的:跑测试、修 lint、格式化代码...
只有当一个任务经历了至少 10 轮 LLM 对话时,才触发记忆更新——短任务不值得记
子 Agent 不会递归更新记忆——否则会无限套娃
用了白名单机制:只有 4 种情况才写入记忆文件
"我们决定用 PostgreSQL 而不是 MongoDB"——值得记
"帮我跑一下测试"——不值得记,测试结果是临时的
记忆更新也是用子 Agent 执行的——不影响主 Agent 的历史记录
记忆更新流程
Skill 进化:越用越聪明
技能不是写好就定型的。每次执行后,Agent 会根据执行结果自动改进技能——修正步骤、补充边界情况、优化指令。下次调用更稳、更准。
# Agent.run 的收尾阶段:
# 跳过条件:被中断 / 等待反馈 / 子 Agent
unless @is_subagent ||
task_interrupted ||
awaiting_user_feedback
# 1. 技能进化钩子
run_skill_evolution_hooks
# 2. 长期记忆更新(子 Agent)
run_memory_update_subagent
end
每次任务完成后(且没被中断),Agent 会做两件事
第一件:检查刚执行的技能,看执行过程有什么可以改进的
比如发现某个步骤经常失败,就在技能描述里补充注意事项
第二件:判断是否需要更新长期记忆(前面详细讲过了)
技能进化 + 长期记忆 + Time Machine,三者共同构成 OpenClacky 的"自我改进"飞轮。每次使用都在积累经验:技能越来越精准,记忆越来越丰富,改坏了还能回退。这就是"越用越懂你"的技术基础。