howler-agents-core 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- howler_agents_core-0.1.0/.gitignore +69 -0
- howler_agents_core-0.1.0/PKG-INFO +159 -0
- howler_agents_core-0.1.0/README.md +112 -0
- howler_agents_core-0.1.0/pyproject.toml +42 -0
- howler_agents_core-0.1.0/src/howler_agents/__init__.py +7 -0
- howler_agents_core-0.1.0/src/howler_agents/agents/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/agents/base.py +82 -0
- howler_agents_core-0.1.0/src/howler_agents/agents/pool.py +50 -0
- howler_agents_core-0.1.0/src/howler_agents/benchmarks/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/benchmarks/swe_bench_agent.py +1157 -0
- howler_agents_core-0.1.0/src/howler_agents/benchmarks/swe_bench_harness.py +404 -0
- howler_agents_core-0.1.0/src/howler_agents/benchmarks/swe_bench_runner.py +804 -0
- howler_agents_core-0.1.0/src/howler_agents/claude_agent.md +269 -0
- howler_agents_core-0.1.0/src/howler_agents/cli.py +1016 -0
- howler_agents_core-0.1.0/src/howler_agents/config.py +48 -0
- howler_agents_core-0.1.0/src/howler_agents/evolution/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/evolution/directive.py +16 -0
- howler_agents_core-0.1.0/src/howler_agents/evolution/loop.py +183 -0
- howler_agents_core-0.1.0/src/howler_agents/evolution/reproducer.py +159 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/pool.py +68 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/store/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/store/base.py +17 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/store/memory.py +30 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/store/postgres.py +117 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/store/redis.py +70 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/store/sqlite.py +119 -0
- howler_agents_core-0.1.0/src/howler_agents/experience/trace.py +28 -0
- howler_agents_core-0.1.0/src/howler_agents/hivemind/__init__.py +13 -0
- howler_agents_core-0.1.0/src/howler_agents/hivemind/consensus.py +180 -0
- howler_agents_core-0.1.0/src/howler_agents/hivemind/coordinator.py +174 -0
- howler_agents_core-0.1.0/src/howler_agents/hivemind/memory.py +163 -0
- howler_agents_core-0.1.0/src/howler_agents/llm/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/llm/claude_code.py +620 -0
- howler_agents_core-0.1.0/src/howler_agents/llm/roles.py +5 -0
- howler_agents_core-0.1.0/src/howler_agents/llm/router.py +148 -0
- howler_agents_core-0.1.0/src/howler_agents/local_runner.py +656 -0
- howler_agents_core-0.1.0/src/howler_agents/mcp_server.py +1444 -0
- howler_agents_core-0.1.0/src/howler_agents/orchestration/__init__.py +22 -0
- howler_agents_core-0.1.0/src/howler_agents/orchestration/claude_flow.py +251 -0
- howler_agents_core-0.1.0/src/howler_agents/orchestration/detector.py +75 -0
- howler_agents_core-0.1.0/src/howler_agents/orchestration/interface.py +100 -0
- howler_agents_core-0.1.0/src/howler_agents/orchestration/local.py +147 -0
- howler_agents_core-0.1.0/src/howler_agents/persistence/__init__.py +15 -0
- howler_agents_core-0.1.0/src/howler_agents/persistence/db.py +101 -0
- howler_agents_core-0.1.0/src/howler_agents/persistence/migrations.py +153 -0
- howler_agents_core-0.1.0/src/howler_agents/persistence/repo.py +58 -0
- howler_agents_core-0.1.0/src/howler_agents/persistence/sync_client.py +140 -0
- howler_agents_core-0.1.0/src/howler_agents/probes/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/probes/evaluator.py +24 -0
- howler_agents_core-0.1.0/src/howler_agents/probes/registry.py +66 -0
- howler_agents_core-0.1.0/src/howler_agents/py.typed +0 -0
- howler_agents_core-0.1.0/src/howler_agents/selection/__init__.py +1 -0
- howler_agents_core-0.1.0/src/howler_agents/selection/criterion.py +81 -0
- howler_agents_core-0.1.0/src/howler_agents/selection/novelty.py +47 -0
- howler_agents_core-0.1.0/src/howler_agents/selection/performance.py +27 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/agents/howler/actor.md +80 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/agents/howler/coordinator.md +85 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/agents/howler/evaluator.md +114 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/agents/howler/reproducer.md +129 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-agents/SKILL.md +138 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-agents-wiggam/SKILL.md +165 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-agents-wiggam/scripts/setup-wiggam-loop.sh +150 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-auto-evolve/SKILL.md +154 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-evolve/SKILL.md +155 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-init/SKILL.md +177 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-memory/SKILL.md +131 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-setup/SKILL.md +191 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-status/SKILL.md +73 -0
- howler_agents_core-0.1.0/src/howler_agents/templates/skills/howler-sync/SKILL.md +134 -0
- howler_agents_core-0.1.0/tests/_helpers.py +32 -0
- howler_agents_core-0.1.0/tests/conftest.py +30 -0
- howler_agents_core-0.1.0/tests/test_e2e_evolution.py +838 -0
- howler_agents_core-0.1.0/tests/test_evolution_loop.py +60 -0
- howler_agents_core-0.1.0/tests/test_experience.py +75 -0
- howler_agents_core-0.1.0/tests/test_gea_mechanisms.py +658 -0
- howler_agents_core-0.1.0/tests/test_llm_router.py +502 -0
- howler_agents_core-0.1.0/tests/test_paper_validation.py +975 -0
- howler_agents_core-0.1.0/tests/test_pool.py +51 -0
- howler_agents_core-0.1.0/tests/test_probes.py +39 -0
- howler_agents_core-0.1.0/tests/test_selection.py +46 -0
- howler_agents_core-0.1.0/tests/test_swe_bench.py +721 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
.env
|
|
3
|
+
.env.local
|
|
4
|
+
.env.*.local
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.egg-info/
|
|
11
|
+
*.egg
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
.eggs/
|
|
15
|
+
*.whl
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
htmlcov/
|
|
22
|
+
.coverage
|
|
23
|
+
.coverage.*
|
|
24
|
+
|
|
25
|
+
# Node
|
|
26
|
+
node_modules/
|
|
27
|
+
.pnpm-store/
|
|
28
|
+
*.tsbuildinfo
|
|
29
|
+
|
|
30
|
+
# Proto generated
|
|
31
|
+
packages/howler-agents-service/src/howler_agents_service/generated/
|
|
32
|
+
packages/howler-agents-ts/src/generated/
|
|
33
|
+
packages/howler-agents-ui/src/generated/
|
|
34
|
+
|
|
35
|
+
# Build output
|
|
36
|
+
packages/howler-agents-ts/dist/
|
|
37
|
+
packages/howler-agents-ui/.output/
|
|
38
|
+
packages/howler-agents-ui/.vinxi/
|
|
39
|
+
packages/howler-agents-docs/.output/
|
|
40
|
+
packages/howler-agents-docs/.vinxi/
|
|
41
|
+
|
|
42
|
+
# Docker
|
|
43
|
+
.docker/
|
|
44
|
+
|
|
45
|
+
# IDE
|
|
46
|
+
.idea/
|
|
47
|
+
.vscode/
|
|
48
|
+
*.swp
|
|
49
|
+
*.swo
|
|
50
|
+
*~
|
|
51
|
+
.DS_Store
|
|
52
|
+
|
|
53
|
+
# Claude Flow
|
|
54
|
+
.claude-flow/
|
|
55
|
+
.swarm/
|
|
56
|
+
|
|
57
|
+
# Howler Agents local DB
|
|
58
|
+
.howler-agents/
|
|
59
|
+
|
|
60
|
+
# SWE-bench Docker evaluation reports (local artifacts)
|
|
61
|
+
howler-agents__*.json
|
|
62
|
+
logs/
|
|
63
|
+
|
|
64
|
+
# Claude local DB
|
|
65
|
+
.claude/memory.db
|
|
66
|
+
|
|
67
|
+
# Misc
|
|
68
|
+
*.log
|
|
69
|
+
tmp/
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: howler-agents-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core library for Group-Evolving Agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/jbeck018/howler-agents
|
|
6
|
+
Project-URL: Repository, https://github.com/jbeck018/howler-agents
|
|
7
|
+
Project-URL: Documentation, https://github.com/jbeck018/howler-agents
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/jbeck018/howler-agents/issues
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: agent-evolution,gea,howler-agents,llm,multi-agent
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Requires-Dist: aiosqlite>=0.20
|
|
13
|
+
Requires-Dist: claude-agent-sdk>=0.1.41
|
|
14
|
+
Requires-Dist: click>=8.0
|
|
15
|
+
Requires-Dist: litellm>=1.50.0
|
|
16
|
+
Requires-Dist: numpy>=1.26
|
|
17
|
+
Requires-Dist: pydantic>=2.0
|
|
18
|
+
Requires-Dist: scikit-learn>=1.4
|
|
19
|
+
Requires-Dist: structlog>=24.0
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
Requires-Dist: asyncpg>=0.29; extra == 'all'
|
|
22
|
+
Requires-Dist: datasets>=2.0; extra == 'all'
|
|
23
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
24
|
+
Requires-Dist: mcp>=1.0; extra == 'all'
|
|
25
|
+
Requires-Dist: pgvector>=0.3; extra == 'all'
|
|
26
|
+
Requires-Dist: redis[hiredis]>=5.0; extra == 'all'
|
|
27
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'all'
|
|
28
|
+
Requires-Dist: swebench>=4.0; extra == 'all'
|
|
29
|
+
Provides-Extra: bench
|
|
30
|
+
Requires-Dist: datasets>=2.0; extra == 'bench'
|
|
31
|
+
Requires-Dist: swebench>=4.0; extra == 'bench'
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: coverage>=7.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-mock>=3.12; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
|
+
Provides-Extra: mcp
|
|
38
|
+
Requires-Dist: httpx>=0.27; extra == 'mcp'
|
|
39
|
+
Requires-Dist: mcp>=1.0; extra == 'mcp'
|
|
40
|
+
Provides-Extra: postgres
|
|
41
|
+
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
42
|
+
Requires-Dist: pgvector>=0.3; extra == 'postgres'
|
|
43
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == 'postgres'
|
|
44
|
+
Provides-Extra: redis
|
|
45
|
+
Requires-Dist: redis[hiredis]>=5.0; extra == 'redis'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# howler-agents-core
|
|
49
|
+
|
|
50
|
+
Core Python library for Group-Evolving Agents (GEA) -- an evolutionary framework for open-ended self-improvement via experience sharing (arXiv:2602.04837).
|
|
51
|
+
|
|
52
|
+
This package implements the full GEA algorithm: agent pool management, performance-novelty parent selection, shared experience aggregation, group reproduction via meta-LLM, and probe-based capability characterization. It can be used standalone or as the engine behind `howler-agents-service`.
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install howler-agents-core
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Optional dependencies
|
|
61
|
+
|
|
62
|
+
For production use with Postgres (experience store + pgvector KNN) and Redis (hot cache):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install howler-agents-core[postgres] # SQLAlchemy + asyncpg + pgvector
|
|
66
|
+
pip install howler-agents-core[redis] # redis with hiredis
|
|
67
|
+
pip install howler-agents-core[all] # both
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick Usage
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import asyncio
|
|
74
|
+
import uuid
|
|
75
|
+
|
|
76
|
+
from howler_agents import HowlerConfig, EvolutionLoop
|
|
77
|
+
from howler_agents.agents.base import Agent, AgentConfig, FrameworkPatch, TaskResult
|
|
78
|
+
from howler_agents.agents.pool import AgentPool
|
|
79
|
+
from howler_agents.evolution.reproducer import GroupReproducer
|
|
80
|
+
from howler_agents.experience.pool import SharedExperiencePool
|
|
81
|
+
from howler_agents.experience.store.memory import InMemoryStore
|
|
82
|
+
from howler_agents.llm.router import LLMRouter
|
|
83
|
+
from howler_agents.probes.evaluator import ProbeEvaluator
|
|
84
|
+
from howler_agents.probes.registry import ProbeRegistry
|
|
85
|
+
from howler_agents.selection.criterion import PerformanceNoveltySelector
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MyAgent(Agent):
|
|
89
|
+
"""Implement your agent by subclassing Agent."""
|
|
90
|
+
|
|
91
|
+
async def run_task(self, task: dict) -> TaskResult:
|
|
92
|
+
# Your task execution logic here
|
|
93
|
+
return TaskResult(success=True, score=0.8, output="done")
|
|
94
|
+
|
|
95
|
+
async def apply_patch(self, patch: FrameworkPatch) -> None:
|
|
96
|
+
self.patches.append(patch)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def main() -> None:
|
|
100
|
+
config = HowlerConfig(
|
|
101
|
+
population_size=10,
|
|
102
|
+
group_size=3,
|
|
103
|
+
num_iterations=5,
|
|
104
|
+
alpha=0.5,
|
|
105
|
+
num_probes=20,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
store = InMemoryStore()
|
|
109
|
+
experience = SharedExperiencePool(store)
|
|
110
|
+
llm = LLMRouter(config)
|
|
111
|
+
selector = PerformanceNoveltySelector(alpha=config.alpha)
|
|
112
|
+
reproducer = GroupReproducer(llm, experience, config)
|
|
113
|
+
registry = ProbeRegistry()
|
|
114
|
+
registry.register_default_probes(num_probes=config.num_probes)
|
|
115
|
+
probes = ProbeEvaluator(registry)
|
|
116
|
+
|
|
117
|
+
pool = AgentPool()
|
|
118
|
+
for _ in range(config.population_size):
|
|
119
|
+
pool.add(MyAgent(AgentConfig(id=str(uuid.uuid4()))))
|
|
120
|
+
|
|
121
|
+
loop = EvolutionLoop(config, pool, selector, reproducer, experience, probes)
|
|
122
|
+
|
|
123
|
+
tasks = [{"description": "solve a coding problem", "type": "general"}]
|
|
124
|
+
results = await loop.run("my-run", tasks)
|
|
125
|
+
print(f"Best score: {results['best_score']:.3f}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
asyncio.run(main())
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Core Modules
|
|
132
|
+
|
|
133
|
+
| Module | Description |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `howler_agents.agents` | Agent base class, AgentPool, FrameworkPatch |
|
|
136
|
+
| `howler_agents.selection` | Performance scorer, KNN novelty estimator, combined criterion |
|
|
137
|
+
| `howler_agents.experience` | Evolutionary traces, shared experience pool, pluggable stores |
|
|
138
|
+
| `howler_agents.evolution` | Evolution directives, group reproducer, main evolution loop |
|
|
139
|
+
| `howler_agents.probes` | Probe task interface, evaluator, registry |
|
|
140
|
+
| `howler_agents.llm` | LiteLLM-backed router with role-based model dispatch |
|
|
141
|
+
| `howler_agents.config` | `HowlerConfig` with all GEA parameters (K, M, alpha, iterations) |
|
|
142
|
+
|
|
143
|
+
## Configuration
|
|
144
|
+
|
|
145
|
+
`HowlerConfig` accepts all GEA parameters:
|
|
146
|
+
|
|
147
|
+
| Parameter | Default | Description |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `population_size` (K) | 10 | Total agents in population |
|
|
150
|
+
| `group_size` (M) | 3 | Agents per parent group |
|
|
151
|
+
| `num_iterations` | 5 | Evolution generations |
|
|
152
|
+
| `alpha` | 0.5 | Performance vs novelty weight (0=novelty, 1=performance) |
|
|
153
|
+
| `num_probes` | 20 | Probe tasks for capability vector |
|
|
154
|
+
| `task_domain` | "general" | Domain identifier (e.g., "swe-bench", "polyglot") |
|
|
155
|
+
| `role_models` | Claude Sonnet defaults | Per-role LLM model configuration |
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# howler-agents-core
|
|
2
|
+
|
|
3
|
+
Core Python library for Group-Evolving Agents (GEA) -- an evolutionary framework for open-ended self-improvement via experience sharing (arXiv:2602.04837).
|
|
4
|
+
|
|
5
|
+
This package implements the full GEA algorithm: agent pool management, performance-novelty parent selection, shared experience aggregation, group reproduction via meta-LLM, and probe-based capability characterization. It can be used standalone or as the engine behind `howler-agents-service`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install howler-agents-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Optional dependencies
|
|
14
|
+
|
|
15
|
+
For production use with Postgres (experience store + pgvector KNN) and Redis (hot cache):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install howler-agents-core[postgres] # SQLAlchemy + asyncpg + pgvector
|
|
19
|
+
pip install howler-agents-core[redis] # redis with hiredis
|
|
20
|
+
pip install howler-agents-core[all] # both
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import asyncio
|
|
27
|
+
import uuid
|
|
28
|
+
|
|
29
|
+
from howler_agents import HowlerConfig, EvolutionLoop
|
|
30
|
+
from howler_agents.agents.base import Agent, AgentConfig, FrameworkPatch, TaskResult
|
|
31
|
+
from howler_agents.agents.pool import AgentPool
|
|
32
|
+
from howler_agents.evolution.reproducer import GroupReproducer
|
|
33
|
+
from howler_agents.experience.pool import SharedExperiencePool
|
|
34
|
+
from howler_agents.experience.store.memory import InMemoryStore
|
|
35
|
+
from howler_agents.llm.router import LLMRouter
|
|
36
|
+
from howler_agents.probes.evaluator import ProbeEvaluator
|
|
37
|
+
from howler_agents.probes.registry import ProbeRegistry
|
|
38
|
+
from howler_agents.selection.criterion import PerformanceNoveltySelector
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MyAgent(Agent):
|
|
42
|
+
"""Implement your agent by subclassing Agent."""
|
|
43
|
+
|
|
44
|
+
async def run_task(self, task: dict) -> TaskResult:
|
|
45
|
+
# Your task execution logic here
|
|
46
|
+
return TaskResult(success=True, score=0.8, output="done")
|
|
47
|
+
|
|
48
|
+
async def apply_patch(self, patch: FrameworkPatch) -> None:
|
|
49
|
+
self.patches.append(patch)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def main() -> None:
|
|
53
|
+
config = HowlerConfig(
|
|
54
|
+
population_size=10,
|
|
55
|
+
group_size=3,
|
|
56
|
+
num_iterations=5,
|
|
57
|
+
alpha=0.5,
|
|
58
|
+
num_probes=20,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
store = InMemoryStore()
|
|
62
|
+
experience = SharedExperiencePool(store)
|
|
63
|
+
llm = LLMRouter(config)
|
|
64
|
+
selector = PerformanceNoveltySelector(alpha=config.alpha)
|
|
65
|
+
reproducer = GroupReproducer(llm, experience, config)
|
|
66
|
+
registry = ProbeRegistry()
|
|
67
|
+
registry.register_default_probes(num_probes=config.num_probes)
|
|
68
|
+
probes = ProbeEvaluator(registry)
|
|
69
|
+
|
|
70
|
+
pool = AgentPool()
|
|
71
|
+
for _ in range(config.population_size):
|
|
72
|
+
pool.add(MyAgent(AgentConfig(id=str(uuid.uuid4()))))
|
|
73
|
+
|
|
74
|
+
loop = EvolutionLoop(config, pool, selector, reproducer, experience, probes)
|
|
75
|
+
|
|
76
|
+
tasks = [{"description": "solve a coding problem", "type": "general"}]
|
|
77
|
+
results = await loop.run("my-run", tasks)
|
|
78
|
+
print(f"Best score: {results['best_score']:.3f}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
asyncio.run(main())
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Core Modules
|
|
85
|
+
|
|
86
|
+
| Module | Description |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `howler_agents.agents` | Agent base class, AgentPool, FrameworkPatch |
|
|
89
|
+
| `howler_agents.selection` | Performance scorer, KNN novelty estimator, combined criterion |
|
|
90
|
+
| `howler_agents.experience` | Evolutionary traces, shared experience pool, pluggable stores |
|
|
91
|
+
| `howler_agents.evolution` | Evolution directives, group reproducer, main evolution loop |
|
|
92
|
+
| `howler_agents.probes` | Probe task interface, evaluator, registry |
|
|
93
|
+
| `howler_agents.llm` | LiteLLM-backed router with role-based model dispatch |
|
|
94
|
+
| `howler_agents.config` | `HowlerConfig` with all GEA parameters (K, M, alpha, iterations) |
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
`HowlerConfig` accepts all GEA parameters:
|
|
99
|
+
|
|
100
|
+
| Parameter | Default | Description |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| `population_size` (K) | 10 | Total agents in population |
|
|
103
|
+
| `group_size` (M) | 3 | Agents per parent group |
|
|
104
|
+
| `num_iterations` | 5 | Evolution generations |
|
|
105
|
+
| `alpha` | 0.5 | Performance vs novelty weight (0=novelty, 1=performance) |
|
|
106
|
+
| `num_probes` | 20 | Probe tasks for capability vector |
|
|
107
|
+
| `task_domain` | "general" | Domain identifier (e.g., "swe-bench", "polyglot") |
|
|
108
|
+
| `role_models` | Claude Sonnet defaults | Per-role LLM model configuration |
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "howler-agents-core"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Core library for Group-Evolving Agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
keywords = ["howler-agents", "gea", "agent-evolution", "llm", "multi-agent"]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"litellm>=1.50.0",
|
|
11
|
+
"numpy>=1.26",
|
|
12
|
+
"scikit-learn>=1.4",
|
|
13
|
+
"pydantic>=2.0",
|
|
14
|
+
"structlog>=24.0",
|
|
15
|
+
"click>=8.0",
|
|
16
|
+
"aiosqlite>=0.20",
|
|
17
|
+
"claude-agent-sdk>=0.1.41",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
howler-agents = "howler_agents.cli:cli"
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
postgres = ["sqlalchemy[asyncio]>=2.0", "asyncpg>=0.29", "pgvector>=0.3"]
|
|
25
|
+
redis = ["redis[hiredis]>=5.0"]
|
|
26
|
+
mcp = ["mcp>=1.0", "httpx>=0.27"]
|
|
27
|
+
bench = ["datasets>=2.0", "swebench>=4.0"]
|
|
28
|
+
all = ["howler-agents-core[postgres,redis,mcp,bench]"]
|
|
29
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "pytest-mock>=3.12", "coverage>=7.0"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/jbeck018/howler-agents"
|
|
33
|
+
Repository = "https://github.com/jbeck018/howler-agents"
|
|
34
|
+
Documentation = "https://github.com/jbeck018/howler-agents"
|
|
35
|
+
"Bug Tracker" = "https://github.com/jbeck018/howler-agents/issues"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["hatchling"]
|
|
39
|
+
build-backend = "hatchling.build"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/howler_agents"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
""""""
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Abstract base agent and configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class AgentConfig:
|
|
13
|
+
"""Configuration state of a single agent."""
|
|
14
|
+
|
|
15
|
+
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
16
|
+
generation: int = 0
|
|
17
|
+
parent_id: str | None = None
|
|
18
|
+
group_id: str | None = None
|
|
19
|
+
framework_config: dict[str, Any] = field(default_factory=dict)
|
|
20
|
+
lineage: list[str] = field(default_factory=list) # full ancestor chain, newest first
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class TaskResult:
|
|
25
|
+
"""Result from an agent performing a task."""
|
|
26
|
+
|
|
27
|
+
success: bool
|
|
28
|
+
score: float
|
|
29
|
+
output: str = ""
|
|
30
|
+
key_decisions: list[str] = field(default_factory=list)
|
|
31
|
+
lessons_learned: list[str] = field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Agent(ABC):
|
|
35
|
+
"""Abstract base for an evolvable agent."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: AgentConfig) -> None:
|
|
38
|
+
self.config = config
|
|
39
|
+
self.performance_score: float = 0.0
|
|
40
|
+
self.novelty_score: float = 0.0
|
|
41
|
+
self.combined_score: float = 0.0
|
|
42
|
+
self.capability_vector: list[float] = []
|
|
43
|
+
self.patches: list[FrameworkPatch] = []
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def id(self) -> str:
|
|
47
|
+
return self.config.id
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
async def run_task(self, task: dict[str, Any]) -> TaskResult:
|
|
51
|
+
"""Execute a task and return the result."""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
async def apply_patch(self, patch: FrameworkPatch) -> None:
|
|
56
|
+
"""Apply an evolutionary mutation to this agent."""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
def clone(self, new_id: str | None = None) -> AgentConfig:
|
|
60
|
+
"""Create a child config from this agent."""
|
|
61
|
+
new_lineage = [self.config.id, *list(self.config.lineage)]
|
|
62
|
+
return AgentConfig(
|
|
63
|
+
id=new_id or str(uuid.uuid4()),
|
|
64
|
+
generation=self.config.generation + 1,
|
|
65
|
+
parent_id=self.config.id,
|
|
66
|
+
group_id=self.config.group_id,
|
|
67
|
+
framework_config=dict(self.config.framework_config),
|
|
68
|
+
lineage=new_lineage,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class FrameworkPatch:
|
|
74
|
+
"""A code/workflow mutation applied to an agent."""
|
|
75
|
+
|
|
76
|
+
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
77
|
+
agent_id: str = ""
|
|
78
|
+
generation: int = 0
|
|
79
|
+
intent: str = ""
|
|
80
|
+
diff: str = ""
|
|
81
|
+
category: str = "general"
|
|
82
|
+
config_updates: dict[str, Any] = field(default_factory=dict)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Agent population pool management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import heapq
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
from howler_agents.agents.base import Agent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AgentPool:
|
|
12
|
+
"""Manages a living population of agents."""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
self._agents: dict[str, Agent] = {}
|
|
16
|
+
|
|
17
|
+
def add(self, agent: Agent) -> None:
|
|
18
|
+
self._agents[agent.id] = agent
|
|
19
|
+
|
|
20
|
+
def remove(self, agent_id: str) -> Agent | None:
|
|
21
|
+
return self._agents.pop(agent_id, None)
|
|
22
|
+
|
|
23
|
+
def get(self, agent_id: str) -> Agent | None:
|
|
24
|
+
return self._agents.get(agent_id)
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def agents(self) -> list[Agent]:
|
|
28
|
+
return list(self._agents.values())
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def size(self) -> int:
|
|
32
|
+
return len(self._agents)
|
|
33
|
+
|
|
34
|
+
def top_k(self, k: int, key: str = "combined_score") -> list[Agent]:
|
|
35
|
+
"""Return the top-K agents by the given score attribute."""
|
|
36
|
+
return heapq.nlargest(k, self._agents.values(), key=lambda a: getattr(a, key, 0.0))
|
|
37
|
+
|
|
38
|
+
def by_generation(self, generation: int) -> list[Agent]:
|
|
39
|
+
return [a for a in self._agents.values() if a.config.generation == generation]
|
|
40
|
+
|
|
41
|
+
def partition_groups(self, group_size: int) -> list[list[Agent]]:
|
|
42
|
+
"""Partition current population into groups of given size."""
|
|
43
|
+
agents = list(self._agents.values())
|
|
44
|
+
return [agents[i : i + group_size] for i in range(0, len(agents), group_size)]
|
|
45
|
+
|
|
46
|
+
def replace_population(self, new_agents: Sequence[Agent]) -> None:
|
|
47
|
+
"""Replace the entire population."""
|
|
48
|
+
self._agents.clear()
|
|
49
|
+
for agent in new_agents:
|
|
50
|
+
self._agents[agent.id] = agent
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SWE-Bench and other benchmark harnesses for howler-agents."""
|