小羽 — a harness coding agent
Project description
小羽 · Xiaoyu
Weaving code, connecting dots, and showing you the best harness architecture.
一个自建的 harness coding agent。
名字取自董永传说里的七仙女天羽——织女织布,小羽织代码。羽毛在传统语义里是飞升与轻盈, 对应这个 agent 想要的手感:行云流水、轻量、无负担。
顺带一个巧合:harness 本身就是织机的部件(提综装置,控制经线升降的那套框架)。 所以"织女 + harness"不是比附,是同一个词。
现在能做什么(v0.4)
- OpenAI 兼容协议接自建 LiteLLM 网关,流式输出
- 六个工具:
read_file(支持offset/limit只读一段)grep(正则搜索,自动跳过.git/node_modules/__pycache__等噪声)list_files(glob 列文件)str_replace(精确替换,主要编辑手段)write_file(整文件覆盖,只用于新建或全量重写)bash
explore子 agent:便宜模型 + 只读工具做检索,返回带路径:行号+ 原文行的结论。 详见下方「explore 与实测数据」- 编辑护栏:改已有文件必须先完整
read_file(bash cat不算,只读一段也不算); 读完之后文件被外部改动会拒绝写入并要求重读;old_str不唯一或匹配不上会带行号提示打回; 行中间开始 + 多行替换会被拦(必然破坏缩进) - 上下文压缩:本地估算 token + 用真实 usage 校准;超阈值时把早期历史交给便宜模型摘要。 原始任务永久保留,摘要不层层累加,压缩后反而更大则放弃
- 写文件和执行命令默认逐个人工确认,
str_replace显示-/+差异预览 - 交互 REPL(
/help/tools/model/usage/context/compact/clear)+ 一次性执行模式 - 按模型分开记账的 token 统计;eval 可横向扫 12 个候选模型并算成本
还没做:接内部工具(飞书 / EDW / Amazon 运营)、TUI、真沙箱隔离。
测试
# 工具层单元测试(不打网络)
.venv/bin/python -m unittest discover -s tests -t . # 130 个测试
# 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
开发模式:
git clone <本仓库> && cd xiaoyu
python3 -m venv .venv
.venv/bin/pip install -e .
配置
首次使用直接跑配置向导(全平台,写到固定的用户级路径,从此不用找 .env 放哪):
xiaoyu config # 交互向导:端点、模型、key(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 |
用
.venv/bin/xiaoyu # 交互模式
.venv/bin/xiaoyu "把 utils.py 里的类型注解补全" # 一次性执行
.venv/bin/xiaoyu --model bedrock-claude-opus-5
REPL 里:/help /tools /model /usage /clear /exit
选模型
默认 主模型 deepseek-v4-pro + 摘要 deepseek-v4-flash,国产便宜模型优先。
依据是 12 个候选模型 × 4 个 case 的实测(xiaoyu/evals/results/*sweep.json):
| 模型 | 相对输入单价 | 4 case | 单个 case 成本 |
|---|---|---|---|
| deepseek-v4-flash | 1× | 4/4 | $0.0026 |
| qwen3.7-plus | 2× | 4/4 | $0.0052 |
| deepseek-v4-pro | 3× | 4/4 | $0.0061 |
| glm-5.2 | 8× | 4/4 | $0.011 |
| gpt-5.6-luna | 7× | 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%) | 没用 |
五组数据给出的结论,每一条都反直觉:
- 用了确实有效(C):主模型上下文砍一半、总成本降 18%。主 agent 从 9 次工具调用降到 3 次。
主模型越贵收益越大——flash 是 1×、
deepseek-v4-pro是 3×,换成 opus-5(37×)差距会拉到十几倍。 - 靠 prompt 引导的采用率只有 1/3(B、D、E 三次里只有 D 主动用了)。措辞劝不动模型。
- 挂上不用也要付钱(B):工具 schema 每轮随请求发送,光是存在就 +5%。工具不能无限加。
- D 组暴露的是真 bug:模型给
read_file传了offset参数(主流 harness 的标准签名), 我们没实现 → 8 次读有 4 次报废。只有走「explore 之后再重读」这条路径才会触发,前三组碰不到。 - 模型重读是合理的:D 组它自己说「链已经清晰了,但让我验证 FORWARD_TO 确实被使用而非 FALLBACK」。 当时 explore 只返回路径行号、没有原文,而任务里警告了有诱饵——不信是对的。 所以现在要求子 agent 必须给出原文证据行,并说明排除了哪些干扰项。
因为第 2 条,采用率改成 harness 层面强制而不是继续改措辞:
连续 3 次 read_file 追加提示,连续 5 次直接拦截并要求改用 explore;
用任何其它工具即重置计数——这样「读那几个马上要改的文件」不会被误伤。
没挂 explore 时该机制完全不触发(劝它用一个不存在的工具是荒谬的)。
方法论上最值钱的一条:「没被调用」不等于「没有用」。A、B 两组里 explore 一次没被调用, 当时差点直接删掉;是 C 组「强制用一次」才量出 -51%。功能没被采用和功能没有价值 是两个独立问题,必须分开验证。
⚠️ 安全
bash 工具会在你机器上执行模型给出的任意命令。默认每条都要你确认,这是唯一的防线。
--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)
├── tests/ 本地测试,130 个,全部不打网络
│ ├── test_tools.py 工具层、编辑护栏、连续读拦截
│ ├── test_context.py token 估算、压缩、消息序列合法性
│ ├── test_readonly_tools.py grep / list_files、explore 只读边界
│ ├── test_agent_paths.py 主循环、中断恢复、摘要回退、REPL 命令(假 client)
│ ├── test_models.py 候选模型、成本计算、横向对比排序
│ └── test_eval_assertions.py eval 断言的双向自证
└── xiaoyu/ 包
├── config.py 运行配置 + .env 解析 + key 读取(永不回显)
├── tools.py 工具注册表、六个工具、护栏
├── agent.py 主循环:流式、tool_calls 累加、审批、压缩触发
├── explore.py explore 子 agent(便宜模型 + 只读工具)
├── compaction.py 上下文压缩:切点、摘要、回退保护
├── tokens.py 本地 token 估算 + 用真实 usage 校准
├── cli.py REPL、斜杠命令、确认交互
├── ui.py ANSI 输出
└── evals/ eval 集(放包内,避免占用 `evals` 这个通用顶层名)
├── harness.py Case/Context + 断言原语
├── cases.py 具体任务
├── models.py 12 个候选模型 + 成本计算
├── prices.json 单价(从 litellm_config.yaml 同步,改价需手工更新)
├── runner.py 执行器(支持 --sweep 横向扫模型)
└── results/ 历史跑分归档(仅在仓库;新结果写到当前目录 xiaoyu-eval-results/)
外层 xiaoyu/ 是仓库、内层是包,这是 Python 的标准形态(同 requests/requests),不是冗余。
路线(按价值排,不按容易排)
— v0.2(严格匹配 + 唯一性校验 + 先读再改 + 失败带提示回错)str_replace编辑工具eval 集— v0.2 建成,但当前没有区分度(12 个模型几乎全满分), 要有依据得补「需要迭代调试 / 大文件多处精确编辑 / 指令自相矛盾 / 长上下文 / 该克制不动」这类硬 case。 按当前模型能力性价比不高,已搁置。上下文压缩— v0.3(本地估算 + usage 校准 + 安全切点 + 摘要不累加 + 变大则回退)模型路由— v0.3/v0.4(摘要与 explore 走便宜模型;主模型默认换成国产便宜模型)— v0.4(便宜模型 + 只读工具 + harness 层面强制采用)explore子 agent- 接内部工具 — 飞书、EDW、Amazon 运营那套。这才是自建 harness 相对 Codex 的真实价值,下一个大动作。
- 真沙箱隔离(容器 /
sandbox-exec)——目前bash在--yolo下没有边界。 - CI(现在测试靠手动跑)、
prices.json自动同步、TUI 精致化。
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xiaoyu_agent-0.5.1.tar.gz.
File metadata
- Download URL: xiaoyu_agent-0.5.1.tar.gz
- Upload date:
- Size: 75.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9f40cac90b1088c1f9458e8784f092578af6e9a60acd1948eb3c020e58dfac9
|
|
| MD5 |
c5ab4ca19ac2808ff0c0017099a75a84
|
|
| BLAKE2b-256 |
3fc9a0e782ae504f951c749f1238a9115c43cd7f8b3a393058b20b4016190a4e
|
File details
Details for the file xiaoyu_agent-0.5.1-py3-none-any.whl.
File metadata
- Download URL: xiaoyu_agent-0.5.1-py3-none-any.whl
- Upload date:
- Size: 58.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13b2bc1e3d61c3317a075a164023fba01ca5290380ed904d1ee32f65de8266a3
|
|
| MD5 |
732b42ff7a14cb1eb7af432c08bcf560
|
|
| BLAKE2b-256 |
e368620a48ba15dc95db6d391eadb5569e4eca3995135394e516f47d1f857eb6
|