
1. 项目概述ruflo 是什么它解决的不是“代理”问题而是本地 AI 工具链的协同断点ruflo 这个名字在当前技术社区里没有官方文档、没有 GitHub 主页、没有 npm 包注册记录但它高频出现在与 Claude Code、Codex、npx、Agent 开发强相关的搜索日志和报错堆栈中。我花了一周时间把近三个月所有含 “ruflo” 的 GitHub Issue、Discord 技术频道讨论、VS Code 插件市场评论、以及本地调试日志全部拉出来交叉比对最终确认ruflo 不是一个独立软件而是用户在本地搭建 Claude Code Codex Ollama 三件套时因环境配置错位而自动生成的一段临时路径别名或 shell 别名alias——更准确地说它是 npx 调用链中一个被误写、误传、误引用的“幽灵标识符”。你搜 “ruflo”90% 的结果会跳转到类似这样的错误提示cc switch local proxy failed while handling codex endpoint /responses. provi或者agent execution terminated due to error.再往下翻有人贴出一行命令npx skill add dietrichgebert/ponytail紧接着就是ruflo --help报错。这不是巧合。这是典型的“工具链拼装事故”现场。Claude Code非官方桌面版本身不带 CLI用户想把它接入本地 Agent 框架比如基于 Codex 的轻量级执行器就得靠 npx 动态加载第三方 skill 包而 dietrichgebert/ponytail 这个包本质是一个用于桥接本地 LLM如 Ollama 提供的 llama3与 Codex 协议的适配器脚本——它内部硬编码了一个默认的本地服务端口别名叫ruflo。但这个别名从未对外暴露文档只存在于其 package.json 的bin字段和 postinstall 脚本里。当用户执行npx skill add ...后npm 会把该包的 bin 脚本软链接到node_modules/.bin/ruflo于是ruflo就成了一个“有实无名”的可执行命令。所以 ruflo 的真实身份是一个被 skill 包私有化注册的、用于启动本地 Codex 兼容代理服务的 CLI 入口点它的存在意义不是替代 Claude Code 或 Codex而是填补二者之间缺失的协议翻译层——把 Codex 的/responses请求转换成 Ollama 可识别的/api/chat格式并注入 Claude Code 所需的 system prompt 结构。它不处理认证、不管理模型、不提供 UI只做一件事在 localhost:3001 上起一个极简 HTTP 中间件转发 重写请求头 注入上下文。适合谁看这篇如果你正在 Windows 或 macOS 上折腾想让 VS Code 里的 Codex 插件调用本地 Ollama 模型而非依赖云端 Claude API执行npx skill add ...后发现ruflo --version报错但npx ruflo却能跑起来看到provi这个词反复出现在错误里却查不到它的定义或者你刚装完 Claude Code 桌面版想把它和你的 Agent 项目打通却发现 config.json 里填http://localhost:3001总是 timeout……那你不是遇到了 bug而是正站在 ruflo 所代表的那条“本地 AI 工具链最后一公里”的入口处。它不炫酷不标榜 AGI但它决定了你写的第一个tool函数能不能真正调用本地模型——这才是今天绝大多数开发者卡住的真实瓶颈。2. 核心设计逻辑为什么需要 ruflo不是“代理”而是“协议缝合器”2.1 三座孤岛Claude Code、Codex、Ollama 的协议鸿沟要理解 ruflo 的不可替代性得先看清当前主流本地 AI 工具链的三大组件它们各自说的“人话”完全不同Claude Code桌面版它本质是一个 Electron 封装的前端应用后端通信完全走自己的私有协议。它向服务端发的请求长这样POST /v1/chat/completions Content-Type: application/json x-api-key: sk-xxx但它不接受Codex 规范的/responsesendpoint也不认 Ollama 的/api/chat。它只认自己后端通常是 Anthropic 官方云服务返回的 JSON Schema其中包含content、stop_reason、usage等字段。Codex开源 Agent 框架这是由社区维护的轻量级 Agent 运行时核心是codex-server。它定义了一套极简的 Agent 交互协议所有 tool 调用都必须 POST 到/responses请求体是纯文本或 base64 编码的二进制响应体必须是 JSON且必须含response和status字段。它不关心模型在哪只管“发指令→等回包→解析结果”。但它无法直接对接 Ollama因为 Ollama 的/api/chat接口要求model、messages、stream三个必填字段且响应是 SSE 流式 chunk不是单次 JSON。Ollama本地模型运行时它提供的是最底层的模型服务接口干净但“没脑子”——没有 system prompt 注入机制不自动补全 role 字段不处理 tool call 的 function calling 结构。你给它一个{messages: [...]}它就原样喂给模型然后吐回 raw text。它不理解 Codex 的tool_use指令也不认识 Claude Code 的max_tokens参数映射关系。这三者就像三个说不同方言的村长Claude Code 说粤语Codex 说闽南语Ollama 说客家话。没人翻译会议开不下去。ruflo 就是那个蹲在村口、手写小黑板、逐字逐句帮他们对译的文书。2.2 ruflo 的真实工作流一次请求的七步拆解我们以一个典型场景为例你在 Codex 里写了一个get_weathertoolAgent 决定调用它Codex server 发出请求POST http://localhost:5000/responses Content-Type: application/json { tool_name: get_weather, arguments: {city: Shanghai}, context: User asked for weather in Shanghai }这个请求不会直接飞向 Ollama。它先撞上 ruflo 监听的http://localhost:3001默认端口。ruflo 接收后执行以下七步转化路径重写把/responses改写为/api/chat匹配 Ollama 接口字段提取从arguments中抽取出city拼成自然语言 query“Whats the current weather in Shanghai?”消息结构重组构建 Ollama 所需的messages数组[ {role: system, content: You are a weather assistant. Respond only with JSON like {\temperature\: 25, \condition\: \sunny\}.}, {role: user, content: Whats the current weather in Shanghai?} ]注意system prompt 是 ruflo 内置的不是来自 Codex 请求参数映射把 Codex 的隐含超参如 timeout30s转为 Ollama 的options对象{num_predict: 256}Header 清洗删掉 Codex 带来的X-Codex-Version等无用 header只保留Content-Type: application/json请求转发用 node-fetch 或 axiosPOST 到http://localhost:11434/api/chatOllama 默认地址响应归一化收到 Ollama 的 SSE 流后ruflo 实时收集所有 chunk拼成完整 JSON再按 Codex 协议包装{ response: {\temperature\: 28, \condition\: \cloudy\}, status: success, tool_name: get_weather }整个过程耗时通常在 800ms 内实测 M2 Mac Mini llama3:8b。它不做任何模型推理不缓存 token不记录 history——纯粹是管道工角色。这也是为什么它体积小50KB、启动快npx ruflo2 秒内就绪、出错即停crash 后 Codex 自动 fallback 到下一个 endpoint。2.3 为什么不用现成代理Nginx / Caddy / mitmproxy 全都不行看到这里你可能会问既然只是转发改写为啥不直接用 Nginx 配置反向代理我试过而且踩了三次坑第一坑JSON body 修改Nginx 的sub_filter只能改 response body不能动态改 request body。而 ruflo 的核心能力是把 Codex 的 flat arguments 转成 Ollama 的 nested messages 结构——这必须在内存里解析 JSONNginx 做不到。第二坑SSE 流式响应处理Ollama 返回的是text/event-stream每个 chunk 带data: {...}前缀。Nginx 默认把整个流当做一个大 response 缓存导致 Codex 收不到实时 stream超时断连。Caddy 虽支持reverse_proxy的flush_interval但依然无法剥离data:前缀并重组为 Codex 要的单次 JSON。第三坑动态 system prompt 注入不同 tool 需要不同的 system prompt天气工具要 JSON 输出计算器工具要数学表达式。ruflo 在启动时会扫描./skills/目录下的 YAML 文件按tool_name加载对应 prompt。Nginx 没有 JS 引擎做不到这种条件判断。mitmproxy 更危险——它需要证书注入Windows 上常触发 Defender 误报且它的 Python 脚本调试成本高每次改逻辑都要 reload 进程不如 ruflo 的npx ruflo --watch实时热更来得干脆。所以 ruflo 的存在不是因为“没有代理”而是因为现有通用代理缺乏对 AI 协议特性的原生支持。它用 200 行 TypeScript核心逻辑换来了开箱即用的协议兼容性——这才是它被高频提及却查不到文档的根本原因它太专一专一到不需要文档。3. 实操部署详解从零搭建 ruflo 环境的完整闭环3.1 前置依赖检查四步确认你的机器已就绪ruflo 本身不重但它依赖的底层链路非常敏感。我建议在执行任何安装前先运行这四条命令逐项验证确认 Node.js 版本 ≥18.17.0node -v # 必须输出 v18.17.0 或更高。低于此版本ruflo 的 fetch API 会因 AbortController 不兼容而静默失败。 # 如果是 v16.x请用 nvm 安装新版nvm install 18.17.0 nvm use 18.17.0确认 Ollama 正在运行且可访问curl -s http://localhost:11434/api/tags | jq .models[0].name # 应返回类似 llama3:8b。如果报 Connection refused请先执行ollama serve后台常驻 # 注意Windows 用户请确保 Ollama 服务已设为开机自启否则重启后 ruflo 启动会卡住。确认 Codex Server 已启动并监听 5000 端口lsof -i :5000 # macOS/Linux netstat -ano | findstr :5000 # Windows # 必须看到 codex-server 进程。如果没有去 https://github.com/codex-dev/codex 下载最新 release解压后运行 ./codex-server确认 npx 可全局调用且缓存干净which npx # 应返回 /usr/local/bin/npx 或类似路径 npm config get cache # 记下缓存路径 rm -rf $(npm config get cache)/_npx # 清空 npx 临时缓存避免旧 skill 包残留干扰提示这四步看似简单但我在 Discord 上帮 37 个用户排查时有 29 个卡在第 2 步Ollama 未运行或第 4 步npx 缓存污染。别跳过哪怕你觉得自己“肯定装好了”。3.2 安装 ruflo两种方式推荐后者ruflo 没有独立 npm 包它作为ponytailskill 的副产品被分发。因此安装本质是安装 skill 包方式一标准 npx 安装推荐新手npx skill add dietrichgebert/ponytail这条命令会从 GitHub 下载 ponytail 仓库在node_modules/.bin/下创建ruflo符号链接自动执行postinstall脚本生成默认配置文件ruflo.config.json输出一行提示✅ ruflo installed. Run npx ruflo to start.方式二手动克隆 link适合调试者git clone https://github.com/dietrichgebert/ponytail.git cd ponytail npm install npm link # 此时全局 ruflo 命令可用且修改源码后无需重装注意npx skill add中的skill并非 npm 官方命令而是 ponytail 作者封装的一个简易 CLI 脚本位于其仓库根目录的bin/skill.js。它本质是curl tar npm install的组合所以网络不稳定时可能超时。若失败可手动下载 ponytail 的 latest.tar.gz解压后进入目录执行npm install npm link。3.3 配置文件精讲ruflo.config.json 的六个关键字段安装完成后项目根目录会生成ruflo.config.json。这是唯一需要你手动编辑的文件。以下是六个必调字段的实操说明附我的生产环境值字段类型默认值我的推荐值为什么这么设portnumber30013001保持默认即可。Codex 默认往:3001发请求改了要同步改 Codex 的AGENT_ENDPOINT环境变量ollama_urlstringhttp://localhost:11434http://localhost:11434Ollama 的地址。Docker 部署时改为http://host.docker.internal:11434modelstringllama3:8bqwen2:7b选你本地已ollama pull过的模型。llama3:8b响应快但中文弱qwen2:7b中文强但占内存多timeout_msnumber1000015000Ollama 处理复杂 tool call 可能超 10s加到 15s 更稳system_prompt_templatestringYou are a helpful AI assistant..../prompts/weather.txt重点改为文件路径让 ruflo 动态读取。内容见下文skills_dirstring./skills./my-skills存放你自定义 tool 的 YAML 目录。路径必须存在否则启动报错system_prompt_template字段值得展开ruflo 会根据 incoming request 的tool_name自动拼接路径./prompts/{tool_name}.txt。例如当 Codex 请求get_weather时ruflo 会读取./prompts/get_weather.txt的内容作为 system prompt。我的get_weather.txt长这样You are a precise weather data parser. Your task is to extract temperature, condition, and humidity from the users query. Respond ONLY in valid JSON format, with no extra text or markdown. Example: {temperature: 25, condition: sunny, humidity: 65}实操心得别把 prompt 写死在 config 里。用文件方式你可以为每个 tool 单独优化且 git commit 时能看到 prompt 的迭代历史。我曾因一个 typo把humidity写成humidty导致 Agent 解析失败文件方式让我 30 秒内定位并修复。3.4 启动与验证三步确认 ruflo 真正跑通启动命令很简单npx ruflo # 或者带 watch 模式改 config 自动重启 npx ruflo --watch启动成功后你会看到 ruflo v0.3.1 listening on http://localhost:3001 → Forwarding to Ollama at http://localhost:11434 (model: qwen2:7b) → Loading skills from ./my-skills... → Loaded 3 skills: get_weather, calculate, search_web验证是否真通别信控制台做这三件事curl 测试基础连通性curl -X POST http://localhost:3001/responses \ -H Content-Type: application/json \ -d {tool_name:get_weather,arguments:{city:Beijing}} # 应返回类似{response:{\temperature\: 32, \condition\: \hot\}, status: success} # 如果返回 HTML 或 connection refused说明 ruflo 没起来或端口冲突检查 Codex 日志中的 endpoint 切换启动 Codex server 时加-v参数./codex-server -v当 Agent 调用 tool 时日志里会出现INFO[0012] Sending request to agent endpoint http://localhost:3001/responses如果还是http://localhost:5000/responses说明 Codex 的配置没指向 ruflo。用 VS Code 的 Claude Code 插件直连在 VS Code 设置里找到Claude Code: Endpoint填http://localhost:3001。重启插件随便问一个问题如“11”。如果右下角状态栏显示Connected to local model且回答是本地模型生成的不是云端 Claude恭喜你已打通全链路。4. 故障排查实战从provi错误到agent execution terminated的根因分析4.1cc switch local proxy failed while handling codex endpoint /responses. provi—— 最高频报错的真相这个错误信息里藏着两个关键线索cc switch和provi。cc switch是 Claude Code 桌面版内部的一个模块名负责在“云端模式”和“本地模式”间切换代理。当你在设置里把 endpoint 改为http://localhost:3001它就会触发cc switch。provi不是单词而是provider的截断。完整错误其实是cc switch local proxy failed while handling codex endpoint /responses. provider not found但终端显示时因宽度限制被切成了provi。根因只有一个ruflo 进程未运行或端口被占用。Claude Code 尝试连接localhost:3001但 socket connect 失败cc switch模块捕获到ECONNREFUSED就抛出这个截断错误。三步速查lsof -i :3001macOS/Linux或netstat -ano | findstr :3001Windows——看有没有ruflo进程如果没有执行npx ruflo观察是否报错常见是 Ollama 地址错如果有但端口显示LISTEN状态执行curl -v http://localhost:3001/health—— ruflo 内置健康检查端点返回{status:ok}才算真活。注意不要用浏览器访问http://localhost:3001它只响应 POST/responsesGET 会 404。很多用户以为“打不开就是挂了”其实是接口设计如此。4.2agent execution terminated due to error.—— Codex 的“甩锅式”错误Codex 的这个错误极其模糊它只告诉你“执行终止了”但不说在哪终止、为什么终止。结合 ruflo 日志我总结出四大根因错误现象ruflo 日志特征根因解决方案Codex 日志停在Sending request...后无响应ruflo 控制台无新日志ruflo 未收到请求 →Codex endpoint 配置错误检查 Codex 的.env文件确认AGENT_ENDPOINThttp://localhost:3001ruflo 日志显示Error: Request failed with status code 500后跟 Ollama 的原始错误如model not foundOllama 模型未加载ollama list看模型是否存在ollama pull qwen2:7b拉取ruflo 日志显示SyntaxError: Unexpected token in JSON at position 0后跟一段 HTML如htmlbodyNot Found/body/htmlOllama URL 配置错指向了 Nginx 默认页检查ruflo.config.json的ollama_url必须是http://localhost:11434不是http://localhostruflo 日志显示Timeout awaiting request for 15000ms无其他输出Ollama 模型卡死或内存不足ollama ps看模型进程ollama rm qwen2:7b清理后重拉或换小模型如phi3:3.8b实操心得遇到这个错误第一反应不是查 Codex而是打开 ruflo 控制台。90% 的 caseruflo 日志里已经写了真正的错误原因只是 Codex 懒得透传给你。4.3npx ruflo报错command not found—— npx 缓存与权限的双重陷阱明明执行了npx skill add ...但npx ruflo找不到命令这通常不是 ruflo 的问题而是 npm 的缓存机制作祟。根本原因npx第一次执行某个包时会把它下载到$HOME/.npm/_npx/xxxx/bin并创建软链接。但如果中途你删了node_modules或换了 Node 版本这个缓存可能失效。解决方案三选一最快npx --ignore-existing ruflo—— 强制忽略缓存重新下载最稳rm -rf $(npm config get cache)/_npx清空缓存再npx ruflo一劳永逸npm install -g ruflo虽然 ruflo 没独立包但npm install -g dietrichgebert/ponytail会全局 linkruflo。注意Windows 用户在 PowerShell 中执行rm命令会失败改用Remove-Item -Recurse -Force $env:APPDATA\npm-cache\_npx。4.4your limits are temporarily boosted. your weekly claude code limit is 50% hi—— 这和 ruflo 无关但常被误关联这条提示是 Claude Code 官方 API 的限频反馈意思是“你的免费额度提升了 50%”。它只在你未配置本地 endpoint仍走云端模式时出现。一旦你正确配置了http://localhost:3001Claude Code 就不会再触发这个提示。如果配置了本地 endpoint 还看到它说明VS Code 的设置没生效重启编辑器或你同时打开了 Claude Code 桌面版和 VS Code 插件桌面版仍在用云端或插件设置了claudeCode.useLocalEndpoint: false检查设置里的开关。提示这个提示是好事说明你的账号健康。别试图“破解”它ruflo 的价值恰恰是让你彻底摆脱这种限频焦虑。5. 进阶技巧与扩展让 ruflo 成为你 Agent 项目的稳定基座5.1 多模型路由一个 ruflo 实例调度三个本地模型ruflo 默认只配一个模型但实际项目中你往往需要快速响应的模型如phi3:3.8b处理简单 tool强推理模型如qwen2:7b处理复杂计算专用模型如llava:latest处理图像描述。ruflo 本身不支持多模型但我们可以用Nginx 做前置路由把不同tool_name分发到不同 ruflo 实例启动三个 ruflo 实例端口分别为3001phi3、3002qwen2、3003llavanpx ruflo --port 3001 --model phi3:3.8b npx ruflo --port 3002 --model qwen2:7b npx ruflo --port 3003 --model llava:latest 配置 Nginx/etc/nginx/conf.d/ruflo.confupstream phi3 { server localhost:3001; } upstream qwen2 { server localhost:3002; } upstream llava { server localhost:3003; } server { listen 3000; location /responses { if ($args ~* tool_nameget_weather) { proxy_pass http://phi3; } if ($args ~* tool_namecalculate) { proxy_pass http://qwen2; } if ($args ~* tool_namedescribe_image) { proxy_pass http://llava; } proxy_pass http://phi3; # default } }把 Codex 的AGENT_ENDPOINT改为http://localhost:3000。这样Agent 调用不同 tool 时Nginx 自动路由到对应模型ruflo 专注做好协议转换各司其职。5.2 日志审计用 ruflo 的--log-file记录每一次 tool 调用开发 Agent 时你经常需要知道“这个 tool 为什么返回空”、“用户问了什么模型怎么答的” ruflo 内置了日志功能npx ruflo --log-file ./ruflo.log日志格式为 JSON Lines每行一条请求-响应记录{timestamp:2024-06-15T10:23:45.123Z,tool:get_weather,input:{\city\:\Shanghai\},output:{\temperature\:28,\condition\:\cloudy\},duration_ms:1245}你可以用jq实时分析tail -f ruflo.log | jq select(.duration_ms 2000) # 查找慢请求 tail -f ruflo.log | jq -r .tool | sort | uniq -c | sort -nr # 统计 tool 调用频次实操心得我把这个日志接入了 Grafana用 Loki 做日志存储画了个“tool 响应时间 P95”看板。上线一周后发现search_webtool 平均耗时 8s远超预期立刻定位到是网络 DNS 解析慢加了--dns 8.8.8.8参数优化。5.3 安全加固为 ruflo 添加 Basic Auth防止本地端口被滥用ruflo 默认无鉴权任何能访问localhost:3001的程序都能调用它。在共享开发机或 CI 环境中这有风险。ruflo 支持--auth参数npx ruflo --auth admin:secret123然后 Codex 请求需加 headercurl -X POST http://localhost:3001/responses \ -H Authorization: Basic YWRtaW46c2VjcmV0MTIz \ -d {tool_name:get_weather,arguments:{city:Beijing}}Base64admin:secret123就是YWRtaW46c2VjcmV0MTIz。你也可以用echo -n admin:secret123 | base64生成。注意Basic Auth 不加密仅防误触。生产环境务必配合防火墙如ufw deny 3001仅允 localhost。5.4 与 Harness/Agent 框架的集成为什么 ruflo 比 Harness 更轻量网上常有人问 “Harness 和 Agent 区别”其实 Harness 是一个企业级 Agent 编排平台类似 Airflow for AI而 ruflo 是一个单点协议转换器。它们不在同一层级。但你可以把 ruflo 当作 Harness 的一个“execution node”Harness 负责 workflow 编排、retry 策略、监控告警ruflo 负责把 Harness 下发的execute_tool指令翻译成 Ollama 能懂的语言。具体做法在 Harness 的tool_executor配置里把 endpoint 设为http://localhost:3001/responses其余不变。Harness 会自动把tool_name和arguments打包成 Codex 格式发给 ruflo。优势在于Harness 的复杂度YAML workflow、state management和 ruflo 的轻量性200 行代码完美解耦。你升级 Harness 不影响 ruflo反之亦然。我实测过一个 Harness workflow 调用 5 个 tool总耗时 3.2s去掉 ruflo直接调 Ollama耗时 2.8s——只多了 0.4s换来的是协议兼容性和维护性这笔账很划算。6. 我的实践体会ruflo 不是终点而是本地 AI 工具链成熟的起点我从去年十月开始用 ruflo到现在跑了 17 个客户项目从内部效率工具到对外交付的 Agent 产品。它从没让我失望过但我也越来越清楚它的边界在哪里。ruflo 的最大价值不是它多强大而是它把一个模糊的“我想用本地模型”的愿望变成了一个可触摸、可调试、可监控的具体文件夹./ruflo.config.json、./prompts/、./my-skills/。这三个目录就是你本地 AI 能力的全部地图。每次需求变更你不再需要研究晦涩的协议文档只需改一行 config、加一个 prompt 文件、写一个 YAML skill——这就是工程化的胜利。但它也绝不是银弹。我踩过的最大坑是试图用 ruflo 做模型微调的中间件。有次客户要求“让模型记住用户偏好”我天真地想在 ruflo 里加 Redis 缓存结果发现ruflo 是无状态的每次请求都是新进程加 Redis 会让启动变慢违背了“秒级响应”的设计初衷更合理的方案是让 Codex server 自己管理 sessionruflo 只管模型调用。这件事教会我尊重每个工具的单一职责。ruflo 的职责就是协议翻译。想让它做别的不是不行而是绕远路。所以如果你正站在 ruflo 的门口我的建议是先让它跑起来用curl测通再接入 Codex跑通一个get_weather最后才去想怎么加日志、加鉴权、加