Skip to content

Commit 30a1cdf

Browse files
authored
Merge pull request #7 from weijt606/v0.2.2
feat(v0.2.2): Pareto selection, novelty filter, backend ensemble & cascade evaluation
2 parents a3d0c40 + dbf30c4 commit 30a1cdf

18 files changed

Lines changed: 1171 additions & 51 deletions

CHANGELOG.md

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,49 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7-
## [0.2.1] - 2026-04-09
7+
## [0.2.2] - 2026-05-24
8+
9+
### Added
10+
- **Pareto-frontier parent selection** (`parent_selection: pareto`) — samples
11+
parents from the set of per-task winners instead of always branching from the
12+
single overall-best candidate, keeping specialists alive as stepping stones to
13+
avoid premature convergence. Inspired by GEPA (arXiv:2507.19457). Reuses the
14+
per-task scores already stored in the search log — no new data collected.
15+
- **Code novelty rejection** (`novelty_filter`, `novelty_threshold`,
16+
`novelty_max_retries`) — detects near-duplicate candidates via stdlib
17+
`difflib` text similarity (no new dependencies) and skips their evaluation to
18+
save API/compute budget. Inspired by ShinkaEvolve (arXiv:2509.19349). Off by
19+
default.
20+
- **Adaptive backend ensemble** (`proposer.ensemble`, `proposer.bandit_c`,
21+
`ph run --ensemble b1,b2,...`) — when several backends are listed, a UCB1
22+
bandit picks one per iteration and shifts picks toward backends that produce
23+
*improving* candidates. Fully deterministic (no RNG) and adds no new
24+
dependencies. Run summary shows a per-backend picks/improve-rate table.
25+
Inspired by ShinkaEvolve's adaptive LLM-ensemble selection.
26+
- **Cascade evaluation** (`evaluator.cascade`, `cascade_threshold`,
27+
`cascade_stage1`) — scores a cheap first subset of tasks and only runs the
28+
rest if it clears the gate, saving budget on weak candidates (AlphaEvolve/
29+
OpenEvolve-style). Per-task mode only; the base harness is always scored in
30+
full. Off by default.
31+
- **Reproducible runs** (`search.seed`) — seeds the RNG so tournament/pareto/
32+
novelty regeneration are repeatable across runs.
33+
- **Observability**`ph log` marks Pareto-frontier members (◆); `ph leaderboard`
34+
adds a Pareto column and a Backend column (shown only when an ensemble was
35+
used). `SearchLog.pareto_win_counts()` powers both the CLI and the orchestrator.
36+
- `proposer_backend` recorded in each candidate's `metadata.json` (ensemble mode)
37+
- Hermes Agent adapter (`hermes`) — 8th proposer backend (`hermes chat -q`)
38+
- `--strategy pareto` and `--ensemble` options for `ph run`
39+
- `proposer/bandit.py` — UCB1 `BackendBandit`
40+
- 31 new tests (206 total)
41+
42+
### Changed
43+
- Agent backends: 7 → 8 (added Hermes Agent)
44+
45+
### Removed
46+
- Stray byte-identical duplicate files (`collector 2.py`, `test_collector 2.py`,
47+
`test_evolution 2.py`) that inflated the test count and tripped ruff N999
48+
49+
850

951
### Added
1052
- `ph shell-hook install/uninstall/status` — zero-config auto-wrap for agent commands via shell preexec hook

README.md

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
1717
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
18-
[![Tests](https://img.shields.io/badge/tests-212%20passing-brightgreen.svg)]()
18+
[![Tests](https://img.shields.io/badge/tests-206%20passing-brightgreen.svg)]()
1919
[![中文文档](https://img.shields.io/badge/文档-中文版-red.svg)](README_CN.md)
2020

2121
---
@@ -53,6 +53,12 @@ PolyHarness fills that gap. It's the open-source engine that makes Meta-Harness
5353
> - Memory tools (like Supermemory) give agents persistent **memory** across conversations.
5454
> - **PolyHarness gives agents persistent self-evolution** — you get a repeatable way to refine how they work over time.
5555
56+
### Part of a wave — specialized for harnesses
57+
58+
PolyHarness doesn't stand alone. A wave of open-source projects has shown that pairing LLMs with evolutionary search systematically improves code and prompts: [GEPA](https://github.com/gepa-ai/gepa) (reflective prompt evolution over a Pareto frontier), [ShinkaEvolve](https://github.com/SakanaAI/ShinkaEvolve) (sample-efficient program evolution), [OpenEvolve](https://github.com/algorithmicsuperintelligence/openevolve) (an open AlphaEvolve), and the [Darwin Gödel Machine](https://sakana.ai/dgm/) (open-ended self-improving agents).
59+
60+
Most of these evolve *general* programs or algorithms. PolyHarness is the member of this wave **specialized for agent harnesses** — the prompts, tool config, and orchestration *around* an existing agent — with a focus on **online evolution from real usage** (`ph wrap``ph evolve`). It borrows the strongest ideas from these projects and applies them to any CLI agent on your own tasks: Pareto-frontier parent selection (GEPA), code-novelty rejection and an adaptive backend ensemble (ShinkaEvolve), and cascade evaluation (AlphaEvolve/OpenEvolve).
61+
5662
## What PolyHarness Is
5763

5864
PolyHarness is the open-source engine for iteratively searching over an agent's harness.
@@ -469,6 +475,16 @@ The Proposer reads **all of this** before generating the next candidate. It can
469475

470476
When you run `ph init --agent claude-code`, PolyHarness automatically generates a `CLAUDE.md` instruction file in the workspace, telling the agent how to behave as an optimization Proposer. Same for `CLAW.md`, `CODEX.md`, `AGENTS.md` (Hermes), `OPENCODE.md` — each agent's native instruction format.
471477

478+
#### Backend ensemble (adaptive selection)
479+
480+
Don't know which backend writes the best harness changes for your task? Let PolyHarness find out. Pass several and it picks one per iteration with a **UCB bandit**, shifting picks toward whichever backend actually produces *improving* candidates:
481+
482+
```bash
483+
ph run --ensemble "claude-code,codex,local"
484+
```
485+
486+
At the end of the run you get a per-backend breakdown (picks + improve-rate). Selection is deterministic given the reward sequence, so runs stay reproducible. Inspired by ShinkaEvolve's adaptive LLM-ensemble selection.
487+
472488
### Local Model Setup
473489

474490
If you're running a local model (Ollama, vLLM, LM Studio, or any OpenAI-compatible server), use the `openai` backend:
@@ -517,10 +533,16 @@ After `ph init`, the workspace has a `config.yaml` with these sections:
517533
search:
518534
max_iterations: 20 # Maximum search iterations
519535
early_stop_patience: 5 # Stop after N iterations with no improvement
520-
parent_selection: best # Strategy: best | tournament | all
536+
parent_selection: best # Strategy: best | tournament | all | pareto
537+
novelty_filter: false # Reject near-duplicate candidates before eval (saves budget)
538+
novelty_threshold: 0.97 # Similarity ratio above which a candidate is a near-duplicate
539+
novelty_max_retries: 1 # Regenerate a near-duplicate this many times before skipping
540+
seed: null # RNG seed — set an int to make randomized runs reproducible
521541
522542
proposer:
523543
backend: api # api | openai | claude-code | claw-code | codex | hermes | opencode | local
544+
ensemble: [] # If non-empty, pick among these backends per iteration via a UCB bandit
545+
bandit_c: 1.41421356 # UCB exploration constant (higher = more exploration)
524546
model: claude-sonnet-4-20250514 # Model name (for api/openai backends)
525547
base_url: null # Custom API endpoint (for openai backend)
526548
api_key: null # API key override (null = use env var)
@@ -532,6 +554,9 @@ evaluator:
532554
type: python # python | docker | custom
533555
entry: evaluate.py # Evaluator script entrypoint
534556
timeout: 300 # Per-task timeout in seconds
557+
cascade: false # Stage cheap subset first; skip rest if it fails the gate (per-task mode)
558+
cascade_threshold: 0.4 # Min stage-1 mean score required to run the full task set
559+
cascade_stage1: 0 # Tasks in stage 1 (0 = auto, ~1/3 of the list)
535560
536561
harness:
537562
language: python # Harness code language
@@ -599,11 +624,11 @@ python -m polyharness --version
599624
| `ph init` | Initialize workspace with auto-copy of harness, tasks, eval script |
600625
| `ph run` | Start the optimization search loop |
601626
| `ph status` | Progress table with elapsed time, improvement rate, and delta |
602-
| `ph log` | Search tree with delta (Δ) column (or `--flat` for table) |
627+
| `ph log` | Search tree with delta (Δ) column and Pareto-frontier (◆) markers (or `--flat` for table) |
603628
| `ph best` | Show best candidate: score, per-task breakdown, changes summary |
604629
| `ph compare A B` | Compare two iterations: score deltas + unified code diff |
605630
| `ph diff <N>` | Shorthand for `compare 0 <N>` |
606-
| `ph leaderboard` | Ranked table of all candidates (`--top N`, `--tasks` drilldown) |
631+
| `ph leaderboard` | Ranked table of all candidates with Pareto (◆) and backend columns (`--top N`, `--tasks` drilldown) |
607632
| `ph trace <N>` | View stdout, stderr, metrics, exit code for an iteration |
608633
| `ph report` | Generate a full markdown report with score trends and per-task table |
609634
| `ph apply` | Copy best harness back to `base_harness/` (or `--target` dir) |
@@ -647,7 +672,8 @@ python -m polyharness --version
647672
--dry-run Only evaluate the base harness, skip search
648673
--resume Continue an interrupted search from where it left off
649674
--backend <name> Override proposer backend without editing config
650-
--strategy <name> Override parent selection: best | tournament | all
675+
--strategy <name> Override parent selection: best | tournament | all | pareto
676+
--ensemble b1,b2,... Pick among multiple backends per iteration via a UCB bandit
651677
```
652678
653679
### `ph wrap` options

README_CN.md

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
1717
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
18-
[![Tests](https://img.shields.io/badge/tests-212%20passing-brightgreen.svg)]()
18+
[![Tests](https://img.shields.io/badge/tests-206%20passing-brightgreen.svg)]()
1919
[![English](https://img.shields.io/badge/Docs-English-blue.svg)](README.md)
2020

2121
---
@@ -53,6 +53,12 @@ PolyHarness 填补了这个空白。它把 Meta-Harness 搜索变成了一个任
5353
> - 记忆工具(如 Supermemory)赋予 agent 跨会话的持久**记忆**
5454
> - **PolyHarness 赋予 agent 持久的自我进化能力**,你可以用可重复运行的方式持续调整它们的工作方式。
5555
56+
### 这波浪潮中的一员——专精 harness
57+
58+
PolyHarness 并非孤例。一批开源项目已经证明:把 LLM 与进化搜索结合,能系统性地改进代码与 prompt——[GEPA](https://github.com/gepa-ai/gepa)(在 Pareto 前沿上做反思式 prompt 进化)、[ShinkaEvolve](https://github.com/SakanaAI/ShinkaEvolve)(样本高效的程序进化)、[OpenEvolve](https://github.com/algorithmicsuperintelligence/openevolve)(AlphaEvolve 的开源实现),以及 [Darwin Gödel Machine](https://sakana.ai/dgm/)(开放式自我改进 agent)。
59+
60+
它们大多进化的是*通用*程序或算法。PolyHarness 是这波浪潮里**专精 agent harness** 的那一员——优化的是包裹在现有 agent *外层*的 prompt、工具配置与编排,并聚焦于**从真实使用中在线进化**`ph wrap``ph evolve`)。它把这些项目中最有效的思路借鉴过来,应用到你自己任务上的任意 CLI agent:Pareto 前沿父代选择(GEPA)、代码新颖性拒绝与自适应后端集成(ShinkaEvolve)、级联评估(AlphaEvolve/OpenEvolve)。
61+
5662
## PolyHarness 是什么
5763

5864
PolyHarness 是一个通过迭代评估与搜索来探索 agent harness 变体的开源引擎。
@@ -469,6 +475,16 @@ Proposer 在生成下一个候选之前会读取**所有这些信息**。它能
469475

470476
当你运行 `ph init --agent claude-code` 时,PolyHarness 会在 workspace 中自动生成 `CLAUDE.md` 指令文件,告诉 agent 如何作为优化 Proposer 工作。`CLAW.md``CODEX.md``AGENTS.md`(Hermes)、`OPENCODE.md` 也是同样的机制,每个 agent 都使用它自己的原生指令格式。
471477

478+
#### 后端集成(自适应择优)
479+
480+
不确定哪个后端最擅长你的任务?让 PolyHarness 替你试。一次传入多个后端,它会用 **UCB bandit** 每轮挑一个,并逐渐把选择倾向"真正产出改进候选"的后端:
481+
482+
```bash
483+
ph run --ensemble "claude-code,codex,local"
484+
```
485+
486+
运行结束会给出每个后端的明细(选中次数 + 改进率)。在给定奖励序列下选择是确定性的,因此运行可复现。该机制借鉴自 ShinkaEvolve 的自适应 LLM 集成选择。
487+
472488
### 本地模型配置
473489

474490
如果你在本地运行模型(Ollama、vLLM、LM Studio 或任何 OpenAI 兼容服务),使用 `openai` 后端:
@@ -517,10 +533,16 @@ proposer:
517533
search:
518534
max_iterations: 20 # 最大搜索迭代次数
519535
early_stop_patience: 5 # 连续 N 轮无改进后停止
520-
parent_selection: best # 父候选选择策略: best | tournament | all
536+
parent_selection: best # 父候选选择策略: best | tournament | all | pareto
537+
novelty_filter: false # 评估前拒绝近重复候选,节省预算
538+
novelty_threshold: 0.97 # 超过此相似度判定为近重复
539+
novelty_max_retries: 1 # 跳过前重新生成近重复候选的次数
540+
seed: null # 随机种子 — 设为整数可让带随机性的搜索可复现
521541
522542
proposer:
523543
backend: api # api | openai | claude-code | claw-code | codex | hermes | opencode | local
544+
ensemble: [] # 非空时,每轮用 UCB bandit 在这些后端中择优
545+
bandit_c: 1.41421356 # UCB 探索常数(越大越偏探索)
524546
model: claude-sonnet-4-20250514 # 模型名称(api/openai 后端使用)
525547
base_url: null # 自定义 API 端点(openai 后端使用)
526548
api_key: null # API 密钥覆盖(null = 使用环境变量)
@@ -532,6 +554,9 @@ evaluator:
532554
type: python # python | docker | custom
533555
entry: evaluate.py # 评估脚本入口
534556
timeout: 300 # 每个任务的超时时间(秒)
557+
cascade: false # 先评便宜的任务子集,未过门槛则跳过其余(逐任务模式)
558+
cascade_threshold: 0.4 # 进入完整任务集所需的第一阶段最低均分
559+
cascade_stage1: 0 # 第一阶段任务数(0 = 自动,约占 1/3)
535560
536561
harness:
537562
language: python # Harness 代码语言
@@ -599,11 +624,11 @@ python -m polyharness --version
599624
| `ph init` | 初始化 workspace,自动复制 harness、任务、评估脚本 |
600625
| `ph run` | 启动优化搜索循环 |
601626
| `ph status` | 进度表格,包含耗时、改进率和增量 |
602-
| `ph log` | 搜索树带增量(Δ),或用 `--flat` 查看表格视图 |
627+
| `ph log` | 搜索树带增量(Δ)列和 Pareto 前沿(◆)标记,或用 `--flat` 查看表格视图 |
603628
| `ph best` | 展示最佳候选:分数、逐任务明细、变更摘要 |
604629
| `ph compare A B` | 对比两个迭代:分数差异 + 统一代码 diff |
605630
| `ph diff <N>` | `compare 0 <N>` 的快捷方式 |
606-
| `ph leaderboard` | 候选排名表(`--top N`、`--tasks` 展开每题分数) |
631+
| `ph leaderboard` | 候选排名表,含 Pareto(◆)与后端列(`--top N`、`--tasks` 展开每题分数) |
607632
| `ph trace <N>` | 查看某次迭代的 stdout、stderr、metrics、退出码 |
608633
| `ph report` | 生成完整 markdown 报告,包含分数趋势和逐任务表格 |
609634
| `ph apply` | 将最优 harness 回写到 `base_harness/`,或通过 `--target` 指定目录 |
@@ -647,7 +672,8 @@ python -m polyharness --version
647672
--dry-run 仅评估基线 harness,跳过搜索
648673
--resume 从上次中断处继续搜索
649674
--backend <name> 覆盖 proposer 后端,无需修改配置
650-
--strategy <name> 覆盖父候选选择策略: best | tournament | all
675+
--strategy <name> 覆盖父候选选择策略: best | tournament | all | pareto
676+
--ensemble b1,b2,... 每轮用 UCB bandit 在多个后端中择优
651677
```
652678
653679
### `ph wrap` 选项

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "polyharness",
3-
"version": "0.2.1",
3+
"version": "0.2.2",
44
"description": "Make your AI agent evolve automatically through iterative harness optimization.",
55
"keywords": ["agent", "harness", "optimization", "meta-harness", "cli"],
66
"license": "MIT",

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "polyharness"
7-
version = "0.2.1"
7+
version = "0.2.2"
88
description = "Automated harness optimization for AI agents — make your agent evolve."
99
readme = "README.md"
1010
license = "MIT"

src/polyharness/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""PolyHarness — Automated harness optimization for AI agents."""
22

3-
__version__ = "0.2.1"
3+
__version__ = "0.2.2"

0 commit comments

Comments
 (0)