Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ flowchart LR
| Config-driven: swap LLM provider, model, coding agent | ✅ |
| Aider and Claude Code executor support | ✅ |
| Anthropic and OpenAI planner/evaluator support | ✅ |
| Goal evaluator — stops when mission is "won" | 🔧 PR #5 |
| k-branch parallel exploration (FunSearch / AlphaEvolve style) | 🔧 PR #6 |
| Goal evaluator — stops when mission is "won" | |
| k-branch parallel exploration (FunSearch / AlphaEvolve style) | |
| Process sandbox (firejail / bwrap) for production safety | 🔧 PR #7 |

---
Expand Down Expand Up @@ -293,6 +293,12 @@ coding_agent:
history:
max_entries: 10

# Population-level search: per round, spawn k independent worktrees, score
# each branch's fitness, promote the best, demote the rest to ledger/failed/.
# k=1 (default) is plain single-branch run_once behavior.
parallel:
k_branches: 1

roles:
planner: ["python3", "roles/planner.py"]
executor: ["bash", "roles/executor.sh"]
Expand Down
9 changes: 7 additions & 2 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ flowchart LR
| 配置驱动:随时切换 LLM 提供商、模型、coding agent | ✅ |
| Aider 和 Claude Code executor 支持 | ✅ |
| Anthropic 和 OpenAI 规划器 / 评估器支持 | ✅ |
| 目标评估器——当 mission 完成时自动停止 | 🔧 PR #5 |
| k 路并行探索(FunSearch / AlphaEvolve 模式) | 🔧 PR #6 |
| 目标评估器——当 mission 完成时自动停止 | |
| k 路并行探索(FunSearch / AlphaEvolve 模式) | |
| 进程级沙箱(firejail / bwrap),面向生产环境 | 🔧 PR #7 |

---
Expand Down Expand Up @@ -293,6 +293,11 @@ coding_agent:
history:
max_entries: 10

# 种群级搜索:每轮起 k 个独立 worktree,按 fitness 排名,最高分推进 evolution/accepted,
# 其余写入 ledger/failed/。k=1(默认)等价于单路 run_once。
parallel:
k_branches: 1

roles:
planner: ["python3", "roles/planner.py"]
executor: ["bash", "roles/executor.sh"]
Expand Down
12 changes: 10 additions & 2 deletions evolution_kernel/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ def _run_with_config(args: argparse.Namespace, cfg: EvolutionConfig) -> int:
print(json.dumps({"halted": True, "reason": why}, indent=2, sort_keys=True))
return 3

result = governor.run_once(goal, run_id=args.run_id)
k = cfg.parallel.k_branches
if k > 1:
result = governor.run_once_parallel(goal, k=k)
else:
result = governor.run_once(goal, run_id=args.run_id)
cost_usd, tokens_used = _safe_cost(result.evaluation)
new_state = hard_stops.record_outcome(
state,
Expand Down Expand Up @@ -163,7 +167,11 @@ def _run_loop(
print(json.dumps({"halted": True, "reason": why}, indent=2, sort_keys=True))
return 3

result = governor.run_once(goal, strategy=pending_strategy)
k = cfg.parallel.k_branches
if k > 1:
result = governor.run_once_parallel(goal, k=k, strategy=pending_strategy)
else:
result = governor.run_once(goal, strategy=pending_strategy)
pending_strategy = None
iteration += 1

Expand Down
17 changes: 17 additions & 0 deletions evolution_kernel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ class StrategistConfig:
every_n_rounds: int = 3


@dataclass(frozen=True)
class ParallelConfig:
k_branches: int = 1


@dataclass(frozen=True)
class EvolutionConfig:
mission: str
Expand All @@ -118,6 +123,7 @@ class EvolutionConfig:
history: HistoryConfig = field(default_factory=HistoryConfig)
goal_evaluator: GoalEvaluatorConfig = field(default_factory=GoalEvaluatorConfig)
strategist: StrategistConfig = field(default_factory=StrategistConfig)
parallel: ParallelConfig = field(default_factory=ParallelConfig)
raw: Mapping[str, Any] = field(default_factory=dict)


Expand Down Expand Up @@ -152,6 +158,7 @@ def parse_config(raw: Mapping[str, Any]) -> EvolutionConfig:
history = _parse_history(raw.get("history", {}))
goal_evaluator = _parse_goal_evaluator(raw.get("goal_evaluator", {}))
strategist = _parse_strategist(raw.get("strategist", {}))
parallel = _parse_parallel(raw.get("parallel", {}))

return EvolutionConfig(
mission=mission.strip(),
Expand All @@ -164,6 +171,7 @@ def parse_config(raw: Mapping[str, Any]) -> EvolutionConfig:
history=history,
goal_evaluator=goal_evaluator,
strategist=strategist,
parallel=parallel,
raw=dict(raw),
)

Expand Down Expand Up @@ -313,3 +321,12 @@ def _parse_strategist(value: Any) -> StrategistConfig:
if not isinstance(every_n, int) or isinstance(every_n, bool) or every_n < 1:
raise ConfigError("`strategist.every_n_rounds` must be a positive integer")
return StrategistConfig(enabled=bool(value.get("enabled", False)), every_n_rounds=every_n)


def _parse_parallel(value: Any) -> ParallelConfig:
if not isinstance(value, Mapping):
raise ConfigError("`parallel` must be a mapping")
k = value.get("k_branches", 1)
if not isinstance(k, int) or isinstance(k, bool) or k < 1:
raise ConfigError("`parallel.k_branches` must be a positive integer")
return ParallelConfig(k_branches=k)
Loading
Loading