Skip to main content

小羽 — a harness coding agent

Project description

小羽 · Xiaoyu

ci PyPI

Weaving code, connecting dots, and showing you the best harness architecture.

一个自建的 harness coding agent。零第三方运行依赖(只有 openai SDK), Windows / macOS / Linux 全平台,pip install xiaoyu-agent 即用。

名字取自董永传说里的七仙女天羽——织女织布,小羽织代码。羽毛在传统语义里是飞升与轻盈, 对应这个 agent 想要的手感:行云流水、轻量、无负担。

顺带一个巧合:harness 本身就是织机的部件(提综装置,控制经线升降的那套框架)。 所以"织女 + harness"不是比附,是同一个词。

现在能做什么(v0.10)

  • 任意 OpenAI 兼容端点(LiteLLM / vLLM / 各家官方 API),流式输出
  • 六个基础工具exploreskill 另见下方):
    • read_file(支持 offset / limit 只读一段)
    • grep(正则搜索,自动跳过 .git / node_modules / __pycache__ 等噪声)
    • list_files(glob 列文件,输出统一正斜杠)
    • str_replace(精确替换,主要编辑手段)
    • write_file(整文件覆盖,只用于新建或全量重写)
    • bash(Windows 上自动换 PowerShell 执行,工具描述与 system prompt 按平台生成)
  • explore 子 agent:便宜模型 + 只读工具做检索,返回带 路径:行号 + 原文行的结论。 详见下方「explore 与实测数据」
  • 编辑护栏:改已有文件必须先完整 read_filebash cat 不算,只读一段也不算); 读完之后文件被外部改动会拒绝写入并要求重读;old_str 不唯一或匹配不上会带行号提示打回; 行中间开始 + 多行替换会被拦(必然破坏缩进)
  • 分层上下文回收:超阈值先 microcompact(把较早的大块 read_file/grep/bash 输出替换成占位符——不花模型调用、不磨损结论,够用就不做摘要); 不够再全量摘要——本地估算 token + 用真实 usage 校准,早期历史交给便宜模型总结。 原始任务永久保留,摘要不层层累加,压缩后反而更大则放弃; 断路器:连续两次压缩省不到 10% 就暂停自动压缩(手动 /compact 不受限)
  • 权限规则allow bash(git *) / deny bash(curl *) / allow write_file(src/*) 这类 规则免逐次确认或直接拦截;deny 在任何模式下都生效(包括 --yolo; 复合命令每一段都要被 allow 覆盖,含 $( ) / 反引号 / > 的命令不吃 allow 前缀规则; 规则放用户级 permissions.txt 或仓库 .xiaoyu/permissions.txt,REPL 里 /allow /deny 直接写入、/perm 查看;确认框答 a = 本会话该工具不再问
  • 危险命令硬拦截rm -rf /、fork bomb、mkfsdd 直写块设备、Windows format 等不可撤销操作在任何模式下都不执行,包括 --yolo——审批是"用户想不想",这层是"绝不"
  • 项目级指令文件:读仓库根目录的 AGENTS.md(或 XIAOYU.md / CLAUDE.md,首个命中) 进 system prompt——项目自带的规范(怎么跑测试、代码约定)跟着仓库走,不用每次口头交代
  • 插件工具:第三方包在 entry point 组 xiaoyu.tools 里声明工厂函数, pip install 后自动挂载(这是接内部工具的代码层通道);坏插件只警告不拦启动、 不许覆盖内置工具、未声明的能力按需要确认处理(fail-closed)
  • 会话落盘:交互与一次性执行的每条消息 append 到用户目录 sessions/*.jsonl (首行 meta,压缩/清屏记事件),供事后诊断,也是将来 /resume 的地基
  • SKILL.md 技能:扫描 ~/.agents/skills/(跨客户端规范库)与用户配置目录 skills/, 与 Anthropic / agentskills.io 同形态;渐进披露——索引进 system prompt, 正文由模型用 skill 工具按需加载,/skills 查看; 索引有预算(单条描述 ≤250 字符、总量 ≤上下文窗口 1%),技能装再多也不吃常驻上下文
  • 错误分类与自动恢复:限流/瞬时错误按分类指数退避重试(±25% jitter 错峰, 服务端给了 Retry-After 就听它的),重试只在这一层(SDK 层已关,不会 3×3 叠加); 上下文超限先强制压缩再重试,鉴权错误直接报清楚不空转; 中断(Ctrl-C)后全量扫描补齐悬空的 tool 结果、半截流式回答也入历史,随时能继续对话
  • 循环护栏:撞到单轮工具调用上限时让模型收尾交代(做了什么/剩什么/建议), 不静默截断;连续相同 (工具, 参数) 调用第 3 次附加提示、第 5 次拒绝执行—— 便宜模型容易原地打转,得在 harness 层刹住
  • 工具可用性探测:工具可挂 check_fn,探测不过就不进 schemas、拒绝执行 (/tools 里标记 [不可用]
  • 写文件和执行命令默认逐个人工确认str_replace 显示 -/+ 差异预览
  • 交互 REPL(/help /tools /skills /model /usage /context /compact /perm /allow /deny /clear
    • 一次性执行模式 + 启动横幅;xiaoyu config 配置向导(全平台固定路径,免找 .env
  • 按模型分开记账的 token 统计;eval 可横向扫 12 个候选模型并算成本
  • 跨平台:Windows(PowerShell 分派、输出统一 UTF-8)/ macOS / Linux, CI 三平台 × 两 Python 版本矩阵验证

还没做:接内部工具(飞书 / EDW / Amazon 运营;SKILL.md + 插件 entry point 两条载体已就绪)、 /resume 恢复会话(落盘已就绪)、TUI、真沙箱隔离。

测试

# 单元测试(255 个,全部不打网络;CI 在三平台 × py3.11/3.14 跑同一套)
.venv/bin/python -m unittest discover -s tests -t .

# eval:真实调模型跑端到端任务
.venv/bin/xiaoyu-eval --list
.venv/bin/xiaoyu-eval                            # 全部 case
.venv/bin/xiaoyu-eval --case targeted_edit -v    # 单个 case + 完整输出
.venv/bin/xiaoyu-eval --model bedrock-claude-opus-5 --repeat 3

eval 集在测什么

case 卡的是什么
fix_and_test 修 bug + 加注解 + 自己写测试并真的跑通
targeted_edit 130 行文件里定点改 —— 用 diff 行数上限抓"整文件重写"
readonly_answer 只读任务一个字都不许改 —— 抓"手痒乱动文件"
multi_file_rename 跨 3 文件重命名,改完测试还得过 —— 抓"改一半"

判据全部机械可判(文件内容、diff 规模、测试退出码、用了哪个工具),没有主观评分。 结果存到当前目录 xiaoyu-eval-results/*.json,含 token、耗时、工具调用序列,用来比较改 prompt / 换模型前后的差异。 失败的 case 会额外保存现场(transcript + 最终文件内容)——临时工作区跑完就删,不留现场就没法诊断。

写新 case 的铁律:断言必须双向自证。先喂"已知正确答案"确认全 PASS, 再喂"看似完成但实际错"确认能 FAIL。这条已经固化成 tests/test_eval_assertions.py, 不打网络就能跑,加 case 时顺手补上正反两个 fixture。

踩过的三个坑(都会让你误判成"agent 不行"):

  • 初始文件因为 textwrap.dedent 找不到公共前缀而带着缩进写进去,语法直接错
  • file_contains("2 ") 判断指数退避,占位函数里的 return value * 2 也命中,等于永远通过
  • file_contains("ZeroDivisionError") 判断"处理了除零"——模型抛 ValueError 是同样合理的设计, 指令里没规定异常类型,这个断言等于偷偷加了一条没提的要求。判行为,别判字面

判行为用 python_snippet_ok:探针脚本写到工作区之外的临时文件、以工作区为 cwd 和 PYTHONPATH 运行, 既避开 shell 引号地狱,也不会污染 nothing_written / unchanged_except 的快照。

安装

pip install xiaoyu-agent      # 或 pipx install xiaoyu-agent
xiaoyu --version              # 验证装上了(多 Python 并存时也能确认升级生效在哪个环境)

升级:

pip install --upgrade xiaoyu-agent    # pipx 装的用:pipx upgrade xiaoyu-agent

开发模式:

git clone https://github.com/pholex/xiaoyu.git && cd xiaoyu
python3 -m venv .venv
.venv/bin/pip install -e .

配置

首次使用直接跑配置向导(全平台,写到固定的用户级路径,从此不用找 .env 放哪):

xiaoyu config            # 交互向导:端点、模型、key
xiaoyu config --show     # 查看生效配置与各项来源(key 永不回显)
xiaoyu config --path     # 打印用户级配置文件路径
xiaoyu config --set XIAOYU_MODEL=deepseek-v4-pro   # 非交互写入,可重复

用户级配置文件的位置:macOS / Linux 在 ~/.config/xiaoyu/.env(跟随 $XDG_CONFIG_HOME), Windows 在 %APPDATA%\xiaoyu\.env

也可以手动在任意工作目录放 .env(零依赖自解析,仓库里的已被 .gitignore 排除):

XIAOYU_BASE_URL=https://<你的网关>/v1
XIAOYU_MODEL=bedrock-claude-sonnet-5
XIAOYU_API_KEY=<你的-key>

优先级:真实环境变量 > 当前目录 .env > 项目根 .env > 用户级 .env,所以临时覆盖很方便:

XIAOYU_MODEL=bedrock-claude-opus-5 xiaoyu

macOS 上 key 也可以不落盘,改用 Keychain(.env 里留空即可,会自动回退去读;Windows 上请用 .env 或环境变量):

security add-generic-password -a "$USER" -s "XIAOYU_API_KEY" -U -w
变量 默认值 说明
XIAOYU_BASE_URL —(必填) 任意 OpenAI 兼容 /v1 端点:LiteLLM、vLLM、各家官方 API…
XIAOYU_MODEL deepseek-v4-pro 见下方"选模型"
XIAOYU_API_KEY 端点的 API key
XIAOYU_ENV_FILE 指定 .env 路径,等价于 --env-file

xiaoyu                                  # 交互模式(xy 是等价缩写)
xy "把 utils.py 里的类型注解补全"          # 一次性执行
xiaoyu --model bedrock-claude-opus-5    # 指定模型

REPL 里:/help /tools /skills /model /usage /context /compact /clear /exit

选模型

默认 主模型 deepseek-v4-pro + 摘要 deepseek-v4-flash,国产便宜模型优先。

依据是 12 个候选模型 × 4 个 case 的实测(xiaoyu/evals/results/*sweep.json):

模型 相对输入单价 4 case 单个 case 成本
deepseek-v4-flash 4/4 $0.0026
qwen3.7-plus 4/4 $0.0052
deepseek-v4-pro 4/4 $0.0061
glm-5.2 4/4 $0.011
gpt-5.6-luna 4/4 $0.016
qwen3.7-max 12× 4/4 $0.030
kimi-k3 20× 4/4 $0.034
gpt-5.6-terra 19× 3/4 $0.037
bedrock-claude-sonnet-5 20× 4/4 $0.062
gpt-5.6-sol 37× 4/4 $0.099
bedrock-claude-opus-5 37× 4/4 $0.124
bedrock-claude-fable-5 68× 4/4 $0.147

同一个任务,最贵的比最便宜的高 57 倍,而通过率没差别 —— 所以默认取便宜的。

⚠️ 但这张表不能用来证明"便宜模型够用":12 个模型几乎全部满分,说明 当前 eval 集没有区分度,4 个 case 都是单文件小改或机械重命名,任何能正常调工具的模型都做得到。 用"全都满分"的 eval 选模型等于抛硬币。真要有依据,得补能让模型露馅的 case: 需要迭代调试的、大文件多处精确编辑的、指令自相矛盾需要顶回来的、长上下文触发压缩的、 以及"不该动的别动"。这件事按当前模型能力性价比不高,暂时搁置。

硬活手动升级,别指望默认模型包打天下:

xiaoyu --model bedrock-claude-opus-5     # 启动时指定
# 或 REPL 里随时切:/model bedrock-claude-opus-5

关于 token usage:12 个候选全都回传 usage(含 gpt-5.6-*),所以压缩的 token 校准 在所有模型上都有效。注意这跟"Mantle / Responses API 不回 usage"的经验相反 —— 经 /v1/chat/completions + stream_options.include_usage 这条路是回的。

explore 与实测数据

explore 把检索委托给便宜模型的只读子 agent(默认 deepseek-v4-flash), 它只有 read_file / grep / list_files——绝不给 bash,否则「只读」是空话 (有测试实测跑完这三个工具后整个工作区字节不变)。

在一个「4 层间接跳转 + 每层都有诱饵常量」的多跳追踪任务上量了五组:

配置 主模型 in tok 总成本 是否用了 explore
A 关闭 explore 24165 $0.01168
B 开启,弱引导 25398 (+5%) $0.01238 (+6%) 没用
C 开启,强制使用 11925 (-51%) $0.00957 (-18%) 用了
D 强引导,自主 29097 (+20%) $0.01640 (+40%) 用了,但又重读了 8 个文件
E 修好证据行 + offset 25804 (+7%) $0.01237 (+6%) 没用

五组数据给出的结论,每一条都反直觉:

  1. 用了确实有效(C):主模型上下文砍一半、总成本降 18%。主 agent 从 9 次工具调用降到 3 次。 主模型越贵收益越大——flash 是 1×、deepseek-v4-pro 是 3×,换成 opus-5(37×)差距会拉到十几倍。
  2. 靠 prompt 引导的采用率只有 1/3(B、D、E 三次里只有 D 主动用了)。措辞劝不动模型。
  3. 挂上不用也要付钱(B):工具 schema 每轮随请求发送,光是存在就 +5%。工具不能无限加。
  4. D 组暴露的是真 bug:模型给 read_file 传了 offset 参数(主流 harness 的标准签名), 我们没实现 → 8 次读有 4 次报废。只有走「explore 之后再重读」这条路径才会触发,前三组碰不到。
  5. 模型重读是合理的:D 组它自己说「链已经清晰了,但让我验证 FORWARD_TO 确实被使用而非 FALLBACK」。 当时 explore 只返回路径行号、没有原文,而任务里警告了有诱饵——不信是对的。 所以现在要求子 agent 必须给出原文证据行,并说明排除了哪些干扰项。

因为第 2 条,采用率改成 harness 层面强制而不是继续改措辞: 连续 3 次 read_file 追加提示,连续 5 次直接拦截并要求改用 explore用任何其它工具即重置计数——这样「读那几个马上要改的文件」不会被误伤。 没挂 explore 时该机制完全不触发(劝它用一个不存在的工具是荒谬的)。

方法论上最值钱的一条:「没被调用」不等于「没有用」。A、B 两组里 explore 一次没被调用, 当时差点直接删掉;是 C 组「强制用一次」才量出 -51%。功能没被采用功能没有价值 是两个独立问题,必须分开验证。

⚠️ 安全

bash 工具会在你机器上执行模型给出的任意命令。默认每条都要你确认,这是主要防线; deny 权限规则和危险命令硬拦截在 --yolo 下仍然生效,但覆盖面有限。 --yolo 会关掉逐条确认——只在一次性、可丢弃的目录里用。

已经踩过一次:eval 是无人值守 + --yolo 跑的,某个模型跑 pytest 失败后执行了 pip install pytest,装进了系统 Python 的 site-packages(那个目录 admin 组可写、免 sudo)。 现在 eval 会注入 PIP_REQUIRE_VIRTUALENV=true 挡住这条路,但要清楚: 这只堵了一个具体出口,不是沙箱read_file / str_replace / write_file 有工作区边界检查, bash 没有——它仍能写你有权限的任何地方。真隔离要靠容器或 sandbox-exec

结构

xiaoyu/                     仓库根
├── pyproject.toml          注册 xiaoyu / xy / xiaoyu-eval 三个命令;依赖精确锁版本
├── .env                    运行配置(含 key,已 gitignore)
├── .github/workflows/      CI(三平台矩阵 + 打包验证)与 release(tag → PyPI 自动发布)
├── .githooks/pre-push      本地兜底:push 前跑全部测试
├── experiments/            可复现实验脚本(README 里的数字出处)
├── tests/                  单元测试 255 个,全部不打网络
│   ├── test_tools.py             工具层、编辑护栏、连续读拦截、硬拦截、平台分派
│   ├── test_context.py           token 估算、压缩、断路器、消息序列合法性
│   ├── test_microcompact.py      microcompact 分层回收、压缩摘要 prompt 结构
│   ├── test_permissions.py       权限规则解析、判定管线、会话授权、Agent 集成
│   ├── test_plugins.py           entry_points 插件加载、fail-closed 默认、工具顺序稳定
│   ├── test_readonly_tools.py    grep / list_files、explore 只读边界
│   ├── test_agent_paths.py       主循环、中断恢复、配对补齐、项目指令、打转检测(假 client)
│   ├── test_errors.py            错误分类器、Retry-After/jitter、重试/压缩恢复路径
│   ├── test_skills.py            SKILL.md 解析、扫描、渐进披露、索引预算、check_fn
│   ├── test_user_config.py       xiaoyu config、用户级 .env、平台路径
│   ├── test_session_log.py       会话落盘
│   ├── test_banner.py            启动横幅
│   ├── test_models.py            候选模型、成本计算、横向对比排序
│   └── test_eval_assertions.py   eval 断言的双向自证
└── xiaoyu/                 包
    ├── config.py           运行配置 + .env 解析链 + key 读取(永不回显)
    ├── tools.py            工具注册表、基础工具、护栏、硬拦截、插件加载、平台分派
    ├── permissions.py      权限规则:allow/deny、bash 前缀、路径 glob、会话授权
    ├── agent.py            主循环:流式、tool_calls 累加、审批、分层回收、恢复、循环护栏
    ├── errors.py           API 错误分类器(限流/瞬时/超限/鉴权)+ Retry-After 解析
    ├── explore.py          explore 子 agent(便宜模型 + 只读工具)
    ├── skills.py           SKILL.md 技能:扫描、frontmatter、渐进披露
    ├── compaction.py       上下文压缩:切点、摘要、回退保护、断路器
    ├── session_log.py      会话落盘(JSONL)
    ├── tokens.py           本地 token 估算 + 用真实 usage 校准
    ├── cli.py              REPL、斜杠命令、config 子命令、确认交互
    ├── banner.py           启动横幅(窄终端降级)
    ├── ui.py               ANSI 输出、Windows VT/UTF-8 适配
    └── evals/              eval 集(放包内,避免占用 `evals` 这个通用顶层名)
        ├── harness.py      Case/Context + 断言原语
        ├── cases.py        具体任务
        ├── models.py       12 个候选模型 + 成本计算
        ├── prices.json     单价(随包分发;改价需手工更新)
        ├── runner.py       执行器(支持 --sweep 横向扫模型)
        └── results/        历史跑分归档(仅在仓库;新结果写到当前目录 xiaoyu-eval-results/)

外层 xiaoyu/ 是仓库、内层是包,这是 Python 的标准形态(同 requests/requests),不是冗余。

路线(按价值排,不按容易排)

  1. str_replace 编辑工具 — v0.2(严格匹配 + 唯一性校验 + 先读再改 + 失败带提示回错)
  2. eval 集 — v0.2 建成,但当前没有区分度(12 个模型几乎全满分), 要有依据得补「需要迭代调试 / 大文件多处精确编辑 / 指令自相矛盾 / 长上下文 / 该克制不动」这类硬 case。 按当前模型能力性价比不高,已搁置
  3. 上下文压缩 — v0.3(本地估算 + usage 校准 + 安全切点 + 摘要不累加 + 变大则回退)
  4. 模型路由 — v0.3/v0.4(摘要与 explore 走便宜模型;主模型默认换成国产便宜模型)
  5. explore 子 agent — v0.4(便宜模型 + 只读工具 + harness 层面强制采用)
  6. 接内部工具 — 飞书、EDW、Amazon 运营那套。这才是自建 harness 相对 Codex 的真实价值, 下一个大动作。载体已就绪:v0.8 起支持 SKILL.md(与 ~/.agents/skills/ 规范库直接互通); v0.10 起有代码层通道——entry point 组 xiaoyu.tools,内部工具包 pip install 即挂载。
  7. 真沙箱隔离(容器 / sandbox-exec)——v0.7 先落了危险命令硬拦截兜底, 但 --yolo 下仍无真正边界,沙箱才是完整答案。
  8. CI / 发布流水线 — v0.9.1 全部就位:GitHub Actions 三平台 × 两 Python 版本测试 + 打包验证;推 vX.Y.Z tag 即经 PyPI Trusted Publishing(OIDC,无长期 token)自动发布, 含 tag 与 __version__ 一致性检查;本地 pre-push hook 兜底。 发版流程 = 改 __init__.py 版本号 → commit → 推 tag,其余全自动。
  9. /resume 恢复会话(会话落盘已就绪)、prices.json 自动同步、TUI 精致化。

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

xiaoyu_agent-0.10.2.tar.gz (116.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

xiaoyu_agent-0.10.2-py3-none-any.whl (85.0 kB view details)

Uploaded Python 3

File details

Details for the file xiaoyu_agent-0.10.2.tar.gz.

File metadata

  • Download URL: xiaoyu_agent-0.10.2.tar.gz
  • Upload date:
  • Size: 116.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for xiaoyu_agent-0.10.2.tar.gz
Algorithm Hash digest
SHA256 ad689fa6660d200df2faad50201d42de67a1b6396fc6e4f038b5cec501b86619
MD5 26c81a37e81b4427d3f14313069cfda1
BLAKE2b-256 48776b5fab45bee28f1e919613364f8305812ac3d05af3baeb17d9997a8b3dc2

See more details on using hashes here.

Provenance

The following attestation bundles were made for xiaoyu_agent-0.10.2.tar.gz:

Publisher: release.yml on pholex/xiaoyu

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file xiaoyu_agent-0.10.2-py3-none-any.whl.

File metadata

  • Download URL: xiaoyu_agent-0.10.2-py3-none-any.whl
  • Upload date:
  • Size: 85.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for xiaoyu_agent-0.10.2-py3-none-any.whl
Algorithm Hash digest
SHA256 69d4a755a5fb9b83ea3ea55eac79983f02bc59a5532e8c5948de85999c65d98d
MD5 886adf36933a9487d4b53c0dc0ca5b81
BLAKE2b-256 c8e61f689ea19a85aa70c8f96f438258fa687108c5d2785f879c0674f21e3dd0

See more details on using hashes here.

Provenance

The following attestation bundles were made for xiaoyu_agent-0.10.2-py3-none-any.whl:

Publisher: release.yml on pholex/xiaoyu

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page