diff --git a/.github/workflows/bee-evolver.yaml b/.github/workflows/bee-evolver.yaml
new file mode 100644
index 00000000..a18641b5
--- /dev/null
+++ b/.github/workflows/bee-evolver.yaml
@@ -0,0 +1,63 @@
+name: bee.Evolver
+
+on:
+ schedule:
+ # Every Monday at 06:00 UTC
+ - cron: '0 6 * * 1'
+ workflow_dispatch:
+ inputs:
+ focus:
+ description: 'Optional focus hint for the Evolver (e.g. "improve prompts", "fix heresies")'
+ required: false
+ default: ''
+ type: string
+
+jobs:
+ evolve:
+ name: Evolutionary Cycle
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ issues: write
+
+ steps:
+ - name: Checkout Hive
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v5
+ with:
+ enable-cache: true
+
+ - name: Install Dependencies
+ run: |
+ uv python install 3.12
+ uv sync --directory agents/bee-evolver
+
+ - name: Configure Git Identity
+ run: |
+ git config --global user.name "bee.Evolver"
+ git config --global user.email "evolver@aura.hive"
+
+ - name: Run bee.Evolver
+ env:
+ AURA_LLM__API_KEY: ${{ secrets.MISTRAL_API_KEY }}
+ AURA_LLM__MODEL: "mistral/mistral-large-latest"
+ AURA_LLM__FALLBACK_MODEL: "openai/gpt-4o-mini"
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ GITHUB_EVENT_NAME: ${{ github.event_name }}
+ AURA_TELEGRAM_TOKEN: ${{ secrets.AURA_TELEGRAM_TOKEN }}
+ AURA_BEE_KEEPER__ADMIN_CHAT_ID: ${{ secrets.AURA_BEE_KEEPER__ADMIN_CHAT_ID }}
+ EVOLVER_ASSIGNEE: "zaebee"
+ EVOLVER_FOCUS: ${{ inputs.focus || '' }}
+ EVOLVER_MAX_IMPROVEMENTS: "3"
+ AURA_BEE_EVOLVER__MAX_TOKENS: "2000"
+ MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ run: |
+ uv run --directory agents/bee-evolver python main.py
diff --git a/agents/bee-evolver/hive-manifest.yaml b/agents/bee-evolver/hive-manifest.yaml
new file mode 100644
index 00000000..ac97be99
--- /dev/null
+++ b/agents/bee-evolver/hive-manifest.yaml
@@ -0,0 +1,28 @@
+hive:
+ version: "1.0"
+ description: "bee.Evolver — autonomous Hive self-improvement agent"
+ nucleotides:
+ A: "src/hive/aggregator/__init__.py"
+ T: "src/hive/transformer/__init__.py"
+ C: "src/hive/connector/__init__.py"
+ G: "src/hive/generator/__init__.py"
+ entry_point: "main.py"
+ config: "src/config.py"
+ models: "src/hive/models.py"
+ metabolism: "src/hive/metabolism.py"
+ prompts:
+ - "prompts/bee_evolver.md"
+ triggers:
+ - schedule: "0 6 * * 1"
+ - workflow_dispatch: true
+ outputs:
+ - github_pr
+ - github_issues
+ - telegram_pulse
+ requires_nats: false
+ requires_secrets:
+ - AURA_LLM__API_KEY
+ - GITHUB_TOKEN
+ - GITHUB_REPOSITORY
+ - AURA_TELEGRAM_TOKEN
+ - AURA_BEE_KEEPER__ADMIN_CHAT_ID
diff --git a/agents/bee-evolver/main.py b/agents/bee-evolver/main.py
new file mode 100644
index 00000000..f24ec631
--- /dev/null
+++ b/agents/bee-evolver/main.py
@@ -0,0 +1,50 @@
+import asyncio
+import sys
+from pathlib import Path
+
+# Ensure src/ is on the path so sub-modules can resolve shared imports
+# (mirrors bee-keeper's runtime layout under uv run)
+_src = Path(__file__).parent / "src"
+if str(_src) not in sys.path:
+ sys.path.insert(0, str(_src))
+
+import structlog # noqa: E402
+from config import EvolverSettings # noqa: E402
+from hive.metabolism import EvolverMetabolism # noqa: E402
+
+structlog.configure(
+ processors=[
+ structlog.processors.add_log_level,
+ structlog.processors.TimeStamper(fmt="iso"),
+ structlog.dev.ConsoleRenderer(),
+ ]
+)
+logger = structlog.get_logger(__name__)
+
+
+async def main() -> None:
+ logger.info("bee_evolver_starting")
+
+ settings = EvolverSettings()
+
+ metabolism = EvolverMetabolism(settings)
+ try:
+ observation = await metabolism.execute()
+ except Exception as e:
+ logger.error("bee_evolver_critical_error", error=str(e), exc_info=True)
+ sys.exit(1)
+
+ if not observation.success and not observation.plan:
+ logger.error("bee_evolver_cycle_failed", errors=observation.errors)
+ sys.exit(1)
+
+ logger.info(
+ "bee_evolver_done",
+ pr_url=observation.pr_url,
+ issues=len(observation.issue_urls),
+ telegram_sent=observation.telegram_sent,
+ )
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/agents/bee-evolver/prompts/bee_evolver.md b/agents/bee-evolver/prompts/bee_evolver.md
new file mode 100644
index 00000000..8b49e456
--- /dev/null
+++ b/agents/bee-evolver/prompts/bee_evolver.md
@@ -0,0 +1,53 @@
+# bee.Evolver Persona
+
+You are bee.Evolver, the evolutionary engine of the Aura Hive. Where bee.Keeper guards purity, you drive growth. Your purpose is to analyze the Hive's current state and propose concrete, high-value mutations that make it stronger, cleaner, and more capable.
+
+## Mission
+
+1. **Identify Weaknesses**: Study the git history, open issues, recent heresies, and filesystem structure to find the highest-leverage improvement opportunities.
+2. **Generate Concrete Mutations**: Produce actionable patches, not vague suggestions. Code changes must be valid unified diffs. Prompt updates must be complete file replacements. Issues must have clear acceptance criteria.
+3. **Respect the ATCG Pattern**: All code mutations must preserve the Aggregator → Transformer → Connector → Generator architecture. Never introduce logic that violates nucleotide boundaries.
+4. **Be Frugal**: Prefer small, focused mutations over large rewrites. One well-placed change is worth more than a sprawling refactor.
+
+## Tone
+
+- Precise and purposeful. You are a surgeon, not a demolition crew.
+- Use Hive metaphors sparingly — clarity over flavor.
+- Every improvement must justify its existence with a concrete benefit.
+
+## Rules for Improvements
+
+### Code patches (`type: "code"`)
+- Must be valid unified diff format: `--- a/file\n+++ b/file\n@@ ... @@`
+- Must not break existing tests or imports
+- Must follow the project's conventions: `structlog` for logging, `pydantic-settings` for config, `litellm` for LLM calls
+- Must not introduce `print()` or `os.getenv()` directly
+- Target files must exist in the filesystem map provided
+
+### Prompt updates (`type: "prompt"`)
+- Provide the **full updated file content** as the patch (not a diff)
+- Only target files under `agents/*/prompts/`
+- Improvements should make the agent more effective, precise, or cost-efficient
+
+### Documentation updates (`type: "doc"`)
+- Provide the **full updated file content** as the patch
+- Only target `.md` files that already exist
+- Focus on accuracy and completeness, not marketing language
+
+### GitHub Issues (`type: "issue"`)
+- Use when the improvement requires significant work that cannot be expressed as a small patch
+- Issue body must include: problem statement, proposed solution, acceptance criteria
+- Label suggestions: `enhancement`, `refactor`, `bug`
+
+## Priority Order
+
+When choosing what to improve, prioritize in this order:
+1. Fix existing heresies detected by bee.Keeper (structural violations)
+2. Address open GitHub Issues that have clear, bounded solutions
+3. Improve agent prompts based on observed failure patterns in HIVE_STATE.md
+4. Refactor code for clarity or performance where the git log shows repeated churn
+5. Update documentation that is stale or missing
+
+## Output Contract
+
+Always return valid JSON matching the schema in the task instructions. Never include markdown fences around the JSON. If uncertain about a patch's correctness, prefer `type: "issue"` over a potentially broken `type: "code"` patch.
diff --git a/agents/bee-evolver/pyproject.toml b/agents/bee-evolver/pyproject.toml
new file mode 100644
index 00000000..d5dda40f
--- /dev/null
+++ b/agents/bee-evolver/pyproject.toml
@@ -0,0 +1,33 @@
+[project]
+name = "aura-evolver"
+version = "0.1.0"
+description = "bee.Evolver: Autonomous Hive self-improvement agent"
+readme = "README.md"
+requires-python = ">=3.12"
+dependencies = [
+ "litellm>=1.63.0",
+ "httpx>=0.28.1",
+ "pydantic-settings>=2.12.0",
+ "structlog>=25.5.0",
+ "gitpython>=3.1.0",
+ "aura-core",
+]
+
+[tool.uv.sources]
+aura-core = { workspace = true }
+
+[project.scripts]
+aura-evolver = "main:main"
+
+[tool.uv]
+managed = true
+package = true
+
+[tool.ruff]
+line-length = 88
+target-version = "py312"
+
+[tool.mypy]
+python_version = "3.12"
+strict = true
+ignore_missing_imports = true
diff --git a/agents/bee-evolver/src/__init__.py b/agents/bee-evolver/src/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/agents/bee-evolver/src/config.py b/agents/bee-evolver/src/config.py
new file mode 100644
index 00000000..277a0f54
--- /dev/null
+++ b/agents/bee-evolver/src/config.py
@@ -0,0 +1,51 @@
+from pydantic import AliasChoices, Field
+from pydantic_settings import BaseSettings, SettingsConfigDict
+
+
+class EvolverSettings(BaseSettings):
+ model_config = SettingsConfigDict(
+ env_prefix="AURA_",
+ env_nested_delimiter="__",
+ extra="ignore",
+ populate_by_name=True,
+ )
+
+ llm__api_key: str = Field(..., alias="AURA_LLM__API_KEY")
+ llm__model: str = Field("mistral/mistral-large-latest", alias="AURA_LLM__MODEL")
+ llm__fallback_model: str = Field(
+ "openai/gpt-4o-mini", alias="AURA_LLM__FALLBACK_MODEL"
+ )
+ llm__ollama_base_url: str = Field(
+ "http://localhost:11434", alias="AURA_LLM__OLLAMA_BASE_URL"
+ )
+
+ github_token: str = Field("mock", alias="GITHUB_TOKEN")
+ github_repository: str = Field(..., alias="GITHUB_REPOSITORY")
+ github_event_name: str = Field("manual", alias="GITHUB_EVENT_NAME")
+
+ telegram_token: str = Field("", alias="AURA_TELEGRAM_TOKEN")
+ admin_chat_id: int = Field(
+ 0,
+ validation_alias=AliasChoices(
+ "AURA_BEE_KEEPER__ADMIN_CHAT_ID", "AURA_ADMIN_CHAT_ID"
+ ),
+ )
+
+ evolver_assignee: str = Field("zaebee", alias="EVOLVER_ASSIGNEE")
+ # Optional free-text focus hint passed via workflow_dispatch input
+ evolver_focus: str = Field("", alias="EVOLVER_FOCUS")
+ max_improvements: int = Field(3, alias="EVOLVER_MAX_IMPROVEMENTS")
+ max_tokens: int = Field(2000, alias="AURA_BEE_EVOLVER__MAX_TOKENS")
+
+ # Filesystem scan: directories to exclude (comma-separated)
+ exclude_dirs: list[str] = Field(
+ default=[
+ ".git", ".venv", "node_modules", "__pycache__",
+ "gen-proto", "gen", ".mypy_cache", ".ruff_cache",
+ ],
+ alias="EVOLVER_EXCLUDE_DIRS",
+ )
+ # GitHub Issues pagination limit
+ issues_per_page: int = Field(20, alias="EVOLVER_ISSUES_PER_PAGE")
+ # Max chars of issue body passed to the LLM
+ issue_body_limit: int = Field(500, alias="EVOLVER_ISSUE_BODY_LIMIT")
diff --git a/agents/bee-evolver/src/hive/__init__.py b/agents/bee-evolver/src/hive/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/agents/bee-evolver/src/hive/aggregator/__init__.py b/agents/bee-evolver/src/hive/aggregator/__init__.py
new file mode 100644
index 00000000..2179fae4
--- /dev/null
+++ b/agents/bee-evolver/src/hive/aggregator/__init__.py
@@ -0,0 +1,190 @@
+import os
+import subprocess # nosec
+from pathlib import Path
+from typing import Any
+
+import httpx
+import structlog
+
+from config import EvolverSettings
+from aura_core import find_hive_root
+from ..models import HiveContext
+
+logger = structlog.get_logger(__name__)
+
+_GITHUB_API = "https://api.github.com"
+
+
+class EvolverAggregator:
+ """A - Aggregator: Senses the current state of the Hive."""
+
+ def __init__(self, settings: EvolverSettings) -> None:
+ self.settings = settings
+ self._root: Path = find_hive_root()
+
+ async def perceive(self) -> HiveContext:
+ logger.info("evolver_aggregator_perceive_started")
+
+ git_log = self._get_git_log()
+ hive_state = self._read_hive_state()
+ keeper_prompt = self._read_keeper_prompt()
+ filesystem_map = self._scan_filesystem()
+ recent_heresies = self._extract_recent_heresies(hive_state)
+
+ open_issues: list[dict] = []
+ open_prs: list[dict] = []
+ if self.settings.github_token and self.settings.github_token != "mock": # nosec B105
+ async with httpx.AsyncClient(timeout=15.0) as client:
+ open_issues = await self._fetch_issues(client)
+ open_prs = await self._fetch_prs(client)
+
+ ctx = HiveContext(
+ git_log=git_log,
+ hive_state=hive_state,
+ open_issues=open_issues,
+ open_prs=open_prs,
+ filesystem_map=filesystem_map,
+ keeper_prompt=keeper_prompt,
+ recent_heresies=recent_heresies,
+ focus_hint=self.settings.evolver_focus,
+ )
+ logger.info(
+ "evolver_aggregator_perceive_done",
+ issues=len(open_issues),
+ prs=len(open_prs),
+ heresies=len(recent_heresies),
+ )
+ return ctx
+
+ # ------------------------------------------------------------------
+ # Private helpers
+ # ------------------------------------------------------------------
+
+ def _get_git_log(self) -> str:
+ try:
+ result = subprocess.run( # nosec
+ ["git", "log", "--oneline", "-20"],
+ capture_output=True,
+ text=True,
+ check=False,
+ cwd=str(self._root),
+ )
+ return result.stdout.strip()
+ except Exception as e:
+ logger.warning("git_log_failed", error=str(e))
+ return ""
+
+ def _read_hive_state(self) -> str:
+ path = self._root / "HIVE_STATE.md"
+ if path.exists():
+ return path.read_text()
+ return ""
+
+ def _read_keeper_prompt(self) -> str:
+ path = self._root / "agents/bee-keeper/prompts/bee_keeper.md"
+ if path.exists():
+ return path.read_text()
+ return ""
+
+ def _scan_filesystem(self) -> list[str]:
+ """Recursively scan the repo, pruning excluded dirs before descending.
+
+ Uses os.walk with in-place dirs mutation so excluded directories
+ (e.g. .git, .venv, node_modules) are never traversed at all,
+ which is significantly faster than rglob + post-filter on large repos.
+ """
+ exclude = set(self.settings.exclude_dirs)
+ items: list[str] = []
+ root_str = str(self._root)
+ for dirpath, dirnames, filenames in os.walk(root_str):
+ # Prune excluded dirs in-place to prevent os.walk from descending
+ dirnames[:] = [d for d in dirnames if d not in exclude]
+ for filename in filenames:
+ full = os.path.join(dirpath, filename)
+ items.append(os.path.relpath(full, root_str))
+ return sorted(items)
+
+ def _extract_recent_heresies(self, hive_state: str) -> list[str]:
+ """Pull heresy bullet points from the most recent audit block."""
+ heresies: list[str] = []
+ in_heresies = False
+ for line in hive_state.splitlines():
+ if "Heresies Detected" in line or "Reflective Insights" in line:
+ in_heresies = True
+ continue
+ if in_heresies:
+ stripped = line.strip()
+ if stripped.startswith("- "):
+ heresies.append(stripped[2:])
+ elif stripped.startswith("**") or stripped.startswith("##"):
+ # New section — stop collecting
+ break
+ return heresies[:10]
+
+ async def _fetch_issues(self, client: httpx.AsyncClient) -> list[dict[str, Any]]:
+ """Fetch all open issues across pages, up to issues_per_page total items."""
+ results: list[dict[str, Any]] = []
+ limit = self.settings.issue_body_limit
+ page = 1
+ per_page = min(self.settings.issues_per_page, 100) # GitHub max is 100
+ try:
+ while len(results) < self.settings.issues_per_page:
+ resp = await client.get(
+ f"{_GITHUB_API}/repos/{self.settings.github_repository}/issues",
+ headers=self._gh_headers(),
+ params={"state": "open", "per_page": per_page, "page": page},
+ )
+ if resp.status_code != 200:
+ logger.warning(
+ "github_issues_fetch_failed", status=resp.status_code
+ )
+ break
+ page_data: list[dict[str, Any]] = resp.json()
+ if not page_data:
+ break # no more pages
+ for i in page_data:
+ if "pull_request" in i:
+ continue # issues endpoint also returns PRs
+ results.append(
+ {
+ "number": i["number"],
+ "title": i["title"],
+ "body": (i["body"] or "")[:limit],
+ }
+ )
+ if len(results) >= self.settings.issues_per_page:
+ break
+ if len(page_data) < per_page:
+ break # last page reached
+ page += 1
+ except Exception as e:
+ logger.warning("github_issues_fetch_failed", error=str(e))
+ return results
+
+ async def _fetch_prs(self, client: httpx.AsyncClient) -> list[dict[str, Any]]:
+ try:
+ resp = await client.get(
+ f"{_GITHUB_API}/repos/{self.settings.github_repository}/pulls",
+ headers=self._gh_headers(),
+ params={"state": "open", "per_page": 10},
+ )
+ if resp.status_code == 200:
+ data: list[dict[str, Any]] = resp.json()
+ return [
+ {
+ "number": p.get("number"),
+ "title": p.get("title", ""),
+ "head": p.get("head", {}).get("ref", ""),
+ }
+ for p in data
+ ]
+ except Exception as e:
+ logger.warning("github_prs_fetch_failed", error=str(e))
+ return []
+
+ def _gh_headers(self) -> dict[str, str]:
+ return {
+ "Authorization": f"Bearer {self.settings.github_token}",
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28",
+ }
diff --git a/agents/bee-evolver/src/hive/connector/__init__.py b/agents/bee-evolver/src/hive/connector/__init__.py
new file mode 100644
index 00000000..c7576124
--- /dev/null
+++ b/agents/bee-evolver/src/hive/connector/__init__.py
@@ -0,0 +1,296 @@
+from typing import Any
+
+import httpx
+import structlog
+
+from config import EvolverSettings
+from ..models import EvolutionPlan, EvolverObservation, Improvement
+
+logger = structlog.get_logger(__name__)
+
+_GITHUB_API = "https://api.github.com"
+_TELEGRAM_API = "https://api.telegram.org"
+
+
+class EvolverConnector:
+ """C - Connector: Opens GitHub Issues/PRs and sends Telegram pulse."""
+
+ def __init__(self, settings: EvolverSettings) -> None:
+ self.settings = settings
+
+ async def act(
+ self,
+ plan: EvolutionPlan,
+ branch: str,
+ timestamp: str,
+ apply_errors: list[str],
+ ) -> EvolverObservation:
+ logger.info("evolver_connector_act_started")
+
+ issue_urls: list[str] = []
+ pr_url = ""
+ errors = list(apply_errors)
+
+ async with httpx.AsyncClient(timeout=20.0) as client:
+ has_github = (
+ self.settings.github_token
+ and self.settings.github_token != "mock" # nosec B105
+ )
+
+ if has_github:
+ # 1. Create Issues for issue-type improvements
+ for imp in plan.improvements:
+ if imp.type == "issue" and imp.issue_body:
+ url = await self._create_issue(client, imp)
+ if url:
+ imp.issue_url = url
+ issue_urls.append(url)
+
+ # 2. Open PR if branch has commits
+ if branch:
+ pr_url = await self._open_pr(
+ client, plan, branch, timestamp, issue_urls
+ )
+
+ # 3. Send Telegram pulse (always attempted if configured)
+ tg_sent = await self._send_telegram(
+ client, plan, pr_url, issue_urls, timestamp, errors
+ )
+
+ success = bool(pr_url or plan.hive_is_optimal)
+ return EvolverObservation(
+ success=success,
+ pr_url=pr_url,
+ issue_urls=issue_urls,
+ branch_name=branch,
+ telegram_sent=tg_sent,
+ errors=errors,
+ plan=plan,
+ )
+
+ # ------------------------------------------------------------------
+ # GitHub helpers
+ # ------------------------------------------------------------------
+
+ async def _create_issue(
+ self, client: httpx.AsyncClient, imp: Improvement
+ ) -> str:
+ try:
+ payload: dict[str, Any] = {
+ "title": imp.title,
+ "body": imp.issue_body or imp.description,
+ "labels": ["evolver", "enhancement"],
+ }
+ if self.settings.evolver_assignee:
+ payload["assignees"] = [self.settings.evolver_assignee]
+
+ resp = await client.post(
+ f"{_GITHUB_API}/repos/{self.settings.github_repository}/issues",
+ headers=self._gh_headers(),
+ json=payload,
+ )
+ if resp.status_code == 201:
+ url: str = resp.json().get("html_url", "")
+ logger.info("github_issue_created", title=imp.title, url=url)
+ return url
+ else:
+ logger.warning(
+ "github_issue_creation_failed",
+ status=resp.status_code,
+ body=resp.text[:200],
+ )
+ except Exception as e:
+ logger.error("github_issue_error", error=str(e))
+ return ""
+
+ async def _open_pr(
+ self,
+ client: httpx.AsyncClient,
+ plan: EvolutionPlan,
+ branch: str,
+ timestamp: str,
+ issue_urls: list[str],
+ ) -> str:
+ try:
+ body = self._build_pr_body(plan, issue_urls)
+ payload: dict[str, Any] = {
+ "title": f"🧬 Evolver Cycle: {timestamp}",
+ "head": branch,
+ "base": "main",
+ "body": body,
+ }
+ resp = await client.post(
+ f"{_GITHUB_API}/repos/{self.settings.github_repository}/pulls",
+ headers=self._gh_headers(),
+ json=payload,
+ )
+ if resp.status_code == 201:
+ pr_data = resp.json()
+ pr_url: str = pr_data.get("html_url", "")
+ pr_number: int = pr_data.get("number", 0)
+ logger.info("github_pr_created", url=pr_url)
+
+ # Add labels
+ if pr_number:
+ await self._add_pr_labels(client, pr_number)
+
+ return pr_url
+ else:
+ logger.warning(
+ "github_pr_creation_failed",
+ status=resp.status_code,
+ body=resp.text[:300],
+ )
+ except Exception as e:
+ logger.error("github_pr_error", error=str(e))
+ return ""
+
+ async def _add_pr_labels(
+ self, client: httpx.AsyncClient, pr_number: int
+ ) -> None:
+ try:
+ await client.post(
+ f"{_GITHUB_API}/repos/{self.settings.github_repository}"
+ f"/issues/{pr_number}/labels",
+ headers=self._gh_headers(),
+ json={"labels": ["evolver"]},
+ )
+ except Exception as e:
+ logger.warning("pr_label_failed", error=str(e))
+
+ def _build_pr_body(self, plan: EvolutionPlan, issue_urls: list[str]) -> str:
+ lines = [
+ "## 🧬 Autonomous Hive Evolution",
+ "",
+ f"> {plan.narrative}",
+ "",
+ "### Improvements",
+ "",
+ ]
+ for imp in plan.improvements:
+ icon = {"code": "🔧", "prompt": "🧠", "doc": "📄", "issue": "📋"}.get(
+ imp.type, "•"
+ )
+ lines.append(f"**{icon} [{imp.type}] {imp.title}**")
+ lines.append(f"{imp.description}")
+ if imp.issue_url:
+ lines.append(f"→ Issue: {imp.issue_url}")
+ lines.append("")
+
+ if issue_urls:
+ lines += ["### Related Issues", ""]
+ for url in issue_urls:
+ lines.append(f"- {url}")
+ lines.append("")
+
+ lines += [
+ "---",
+ "_Generated by bee.Evolver — autonomous Hive improvement agent._",
+ "_Review carefully before merging. All commits include `[skip ci]`._",
+ ]
+ return "\n".join(lines)
+
+ # ------------------------------------------------------------------
+ # Telegram helpers
+ # ------------------------------------------------------------------
+
+ async def _send_telegram(
+ self,
+ client: httpx.AsyncClient,
+ plan: EvolutionPlan,
+ pr_url: str,
+ issue_urls: list[str],
+ timestamp: str,
+ errors: list[str],
+ ) -> bool:
+ if not self.settings.telegram_token or not self.settings.admin_chat_id:
+ logger.warning("telegram_not_configured_skipping")
+ return False
+
+ message = self._build_telegram_message(
+ plan, pr_url, issue_urls, timestamp, errors
+ )
+ try:
+ resp = await client.post(
+ f"{_TELEGRAM_API}/bot{self.settings.telegram_token}/sendMessage",
+ json={
+ "chat_id": self.settings.admin_chat_id,
+ "text": message,
+ # HTML is more robust than Markdown V1 — no risk of parse
+ # failures from unescaped _, *, or ` in LLM-generated titles.
+ "parse_mode": "HTML",
+ "disable_web_page_preview": True,
+ },
+ )
+ if resp.status_code == 200:
+ logger.info("telegram_pulse_sent", chat_id=self.settings.admin_chat_id)
+ return True
+ else:
+ logger.warning(
+ "telegram_send_failed",
+ status=resp.status_code,
+ body=resp.text[:200],
+ )
+ except Exception as e:
+ logger.error("telegram_error", error=str(e))
+ return False
+
+ @staticmethod
+ def _h(text: str) -> str:
+ """Escape a string for safe embedding in Telegram HTML messages."""
+ return text.replace("&", "&").replace("<", "<").replace(">", ">")
+
+ def _build_telegram_message(
+ self,
+ plan: EvolutionPlan,
+ pr_url: str,
+ issue_urls: list[str],
+ timestamp: str,
+ errors: list[str],
+ ) -> str:
+ h = self._h
+ if plan.hive_is_optimal:
+ return (
+ f"🍯 bee.Evolver Pulse — {h(timestamp)}\n"
+ "The Hive is crystalline. No mutations required."
+ )
+
+ lines = [f"🧬 bee.Evolver Pulse — {h(timestamp)}", ""]
+
+ if plan.improvements:
+ lines.append(f"Improvements generated: {len(plan.improvements)}")
+ for imp in plan.improvements:
+ icon = {"code": "🔧", "prompt": "🧠", "doc": "📄", "issue": "📋"}.get(
+ imp.type, "•"
+ )
+ lines.append(f"{icon} {h(imp.title)} ({h(imp.type)})")
+ lines.append("")
+
+ lines.append(f"Tokens consumed: {plan.token_usage}")
+
+ if pr_url:
+ lines.append(f"PR: {h(pr_url)}")
+
+ if errors:
+ status = "⚠️ Partial"
+ elif plan.improvements:
+ status = "✅ Cycle complete"
+ else:
+ status = "❌ No improvements generated"
+
+ lines.append(f"Status: {status}")
+
+ if errors:
+ lines.append("")
+ lines.append("Patch errors:")
+ for err in errors[:3]:
+ lines.append(f"• {h(err[:100])}")
+
+ return "\n".join(lines)
+
+ def _gh_headers(self) -> dict[str, str]:
+ return {
+ "Authorization": f"Bearer {self.settings.github_token}",
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28",
+ }
diff --git a/agents/bee-evolver/src/hive/generator/__init__.py b/agents/bee-evolver/src/hive/generator/__init__.py
new file mode 100644
index 00000000..367dd5dc
--- /dev/null
+++ b/agents/bee-evolver/src/hive/generator/__init__.py
@@ -0,0 +1,137 @@
+import subprocess # nosec
+from pathlib import Path
+
+import structlog
+
+from config import EvolverSettings
+from aura_core import find_hive_root
+from ..models import EvolutionPlan, Improvement
+
+logger = structlog.get_logger(__name__)
+
+
+class EvolverGenerator:
+ """G - Generator: Applies improvements to the codebase and pushes a branch."""
+
+ def __init__(self, settings: EvolverSettings) -> None:
+ self.settings = settings
+ self._root: Path = find_hive_root()
+
+ def prepare_branch(self, timestamp: str) -> str:
+ """Create and checkout a new evolver branch. Returns branch name.
+
+ Uses -B (force-reset) so a previously interrupted cycle on the same
+ timestamp doesn't cause a 'branch already exists' failure.
+ """
+ branch = f"evolver/cycle-{timestamp}"
+ try:
+ self._git(["checkout", "-B", branch])
+ logger.info("evolver_branch_created", branch=branch)
+ except Exception as e:
+ logger.error("branch_creation_failed", branch=branch, error=str(e))
+ raise
+ return branch
+
+ def apply_improvements(self, plan: EvolutionPlan) -> list[str]:
+ """
+ Apply all patchable improvements (code/prompt/doc) to the working tree.
+ Returns list of error messages for improvements that could not be applied.
+ """
+ errors: list[str] = []
+ for imp in plan.improvements:
+ if imp.type in ("code", "prompt", "doc") and imp.patch:
+ err = self._apply_patch(imp)
+ if err:
+ errors.append(err)
+ return errors
+
+ def commit_and_push(self, branch: str, timestamp: str) -> bool:
+ """Stage all changes, commit, and push the branch. Returns True on success."""
+ try:
+ status = self._git(["status", "--porcelain"])
+ if not status.strip():
+ logger.info("no_changes_to_commit")
+ return True
+
+ self._git(["add", "-A"])
+ msg = (
+ f"feat(evolver): autonomous improvement cycle {timestamp} [skip ci]\n\n"
+ "Co-authored-by: bee.Evolver "
+ )
+ self._git(["commit", "-m", msg])
+ self._git(["push", "--set-upstream", "origin", branch])
+ logger.info("evolver_branch_pushed", branch=branch)
+ return True
+ except Exception as e:
+ logger.error("commit_push_failed", error=str(e))
+ return False
+
+ # ------------------------------------------------------------------
+ # Private helpers
+ # ------------------------------------------------------------------
+
+ def _apply_patch(self, imp: Improvement) -> str | None:
+ """
+ Apply a single improvement's patch.
+ For prompt/doc types with a target_file, write the patch as full file content.
+ For code type, attempt `git apply` with the unified diff.
+ Returns an error string on failure, None on success.
+ """
+ assert imp.patch is not None # caller guarantees this
+
+ if imp.type in ("prompt", "doc") and imp.target_file:
+ target = self._root / imp.target_file
+ try:
+ target.parent.mkdir(parents=True, exist_ok=True)
+ target.write_text(imp.patch)
+ logger.info("patch_written", file=imp.target_file, type=imp.type)
+ return None
+ except Exception as e:
+ msg = f"Failed to write {imp.target_file}: {e}"
+ logger.warning("patch_write_failed", file=imp.target_file, error=str(e))
+ return msg
+
+ if imp.type == "code":
+ try:
+ result = subprocess.run( # nosec
+ ["git", "apply", "--check", "-"],
+ input=imp.patch,
+ capture_output=True,
+ text=True,
+ check=False,
+ cwd=str(self._root),
+ )
+ if result.returncode != 0:
+ msg = (
+ f"Patch for '{imp.title}' failed dry-run: "
+ f"{result.stderr.strip()[:200]}"
+ )
+ logger.warning("patch_dry_run_failed", title=imp.title)
+ return msg
+
+ subprocess.run( # nosec
+ ["git", "apply", "-"],
+ input=imp.patch,
+ capture_output=True,
+ text=True,
+ check=True,
+ cwd=str(self._root),
+ )
+ logger.info("code_patch_applied", title=imp.title)
+ return None
+ except subprocess.CalledProcessError as e:
+ msg = f"git apply failed for '{imp.title}': {e.stderr[:200]}"
+ logger.warning("code_patch_apply_failed", title=imp.title, error=str(e))
+ return msg
+
+ return None
+
+ def _git(self, args: list[str]) -> str:
+ result = subprocess.run( # nosec
+ ["git"] + args,
+ capture_output=True,
+ text=True,
+ check=True,
+ cwd=str(self._root),
+ )
+ return result.stdout
diff --git a/agents/bee-evolver/src/hive/metabolism.py b/agents/bee-evolver/src/hive/metabolism.py
new file mode 100644
index 00000000..5ac30207
--- /dev/null
+++ b/agents/bee-evolver/src/hive/metabolism.py
@@ -0,0 +1,107 @@
+import subprocess # nosec
+from datetime import UTC, datetime
+
+import structlog
+
+from config import EvolverSettings
+from .aggregator import EvolverAggregator
+from .connector import EvolverConnector
+from .generator import EvolverGenerator
+from .models import EvolverObservation, EvolutionPlan
+from .transformer import EvolverTransformer
+
+logger = structlog.get_logger(__name__)
+
+
+class EvolverMetabolism:
+ """Orchestrates the ATCG flow for the bee.Evolver agent."""
+
+ def __init__(self, settings: EvolverSettings) -> None:
+ self.settings = settings
+ self.aggregator = EvolverAggregator(settings)
+ self.transformer = EvolverTransformer(settings)
+ self.generator = EvolverGenerator(settings)
+ self.connector = EvolverConnector(settings)
+
+ async def execute(self) -> EvolverObservation:
+ """Run one complete evolutionary cycle."""
+ timestamp = datetime.now(UTC).strftime("%Y%m%d-%H%M%S")
+ logger.info("evolver_metabolism_started", timestamp=timestamp)
+
+ # Configure git identity for CI
+ self._configure_git()
+
+ # 1. A — Aggregate: sense the Hive
+ context = await self.aggregator.perceive()
+
+ # 2. T — Transform: produce improvement plan
+ plan: EvolutionPlan = await self.transformer.think(context)
+
+ branch = ""
+ apply_errors: list[str] = []
+
+ if not plan.hive_is_optimal and plan.improvements:
+ patchable = [
+ i for i in plan.improvements if i.type in ("code", "prompt", "doc") and i.patch
+ ]
+
+ if patchable:
+ # 3. G — Generate: apply patches and push branch
+ try:
+ branch = self.generator.prepare_branch(timestamp)
+ apply_errors = self.generator.apply_improvements(plan)
+ pushed = self.generator.commit_and_push(branch, timestamp)
+ if not pushed:
+ apply_errors.append("Failed to push branch to origin.")
+ branch = ""
+ except Exception as e:
+ logger.error("generator_failed", error=str(e))
+ apply_errors.append(f"Generator error: {e}")
+ branch = ""
+ else:
+ logger.info(
+ "no_patchable_improvements_skipping_branch",
+ total=len(plan.improvements),
+ )
+
+ # 4. C — Connect: open Issues/PR + Telegram pulse
+ observation = await self.connector.act(
+ plan=plan,
+ branch=branch,
+ timestamp=timestamp,
+ apply_errors=apply_errors,
+ )
+
+ logger.info(
+ "evolver_metabolism_completed",
+ improvements=len(plan.improvements),
+ pr_url=observation.pr_url,
+ telegram_sent=observation.telegram_sent,
+ errors=len(observation.errors),
+ )
+ return observation
+
+ def _configure_git(self) -> None:
+ """Set git identity scoped to the current repository (not global)."""
+ from aura_core import find_hive_root
+ root = str(find_hive_root())
+ try:
+ subprocess.run( # nosec
+ ["git", "config", "user.name", "bee.Evolver"],
+ check=True,
+ capture_output=True,
+ cwd=root,
+ )
+ subprocess.run( # nosec
+ ["git", "config", "user.email", "evolver@aura.hive"],
+ check=True,
+ capture_output=True,
+ cwd=root,
+ )
+ logger.info("git_identity_configured")
+ except subprocess.CalledProcessError as e:
+ logger.error(
+ "git_config_failed",
+ error=e.stderr.decode(errors="replace").strip(),
+ )
+ raise
diff --git a/agents/bee-evolver/src/hive/models.py b/agents/bee-evolver/src/hive/models.py
new file mode 100644
index 00000000..06c843cd
--- /dev/null
+++ b/agents/bee-evolver/src/hive/models.py
@@ -0,0 +1,61 @@
+from dataclasses import dataclass, field
+from typing import Literal
+
+
+ImprovementType = Literal["code", "prompt", "doc", "issue"]
+
+
+@dataclass
+class Improvement:
+ """A single concrete improvement proposed by the Evolver."""
+
+ type: ImprovementType
+ title: str
+ description: str
+ # File to patch (for code/prompt/doc types)
+ target_file: str | None = None
+ # Unified diff or full replacement content
+ patch: str | None = None
+ # Body for a GitHub Issue (for issue type, or supplementary for others)
+ issue_body: str | None = None
+ # GitHub Issue URL after creation (populated by Connector)
+ issue_url: str | None = None
+
+
+@dataclass
+class HiveContext:
+ """Aggregated snapshot of the Hive's current state."""
+
+ git_log: str = ""
+ hive_state: str = ""
+ open_issues: list[dict] = field(default_factory=list)
+ open_prs: list[dict] = field(default_factory=list)
+ filesystem_map: list[str] = field(default_factory=list)
+ keeper_prompt: str = ""
+ recent_heresies: list[str] = field(default_factory=list)
+ focus_hint: str = ""
+
+
+@dataclass
+class EvolutionPlan:
+ """Output of the Transformer: ordered list of improvements + metabolic summary."""
+
+ improvements: list[Improvement] = field(default_factory=list)
+ # Short narrative for Telegram pulse
+ narrative: str = ""
+ token_usage: int = 0
+ # True if the LLM determined no improvements are needed
+ hive_is_optimal: bool = False
+
+
+@dataclass
+class EvolverObservation:
+ """Result of a full evolutionary cycle (output of Connector)."""
+
+ success: bool
+ pr_url: str = ""
+ issue_urls: list[str] = field(default_factory=list)
+ branch_name: str = ""
+ telegram_sent: bool = False
+ errors: list[str] = field(default_factory=list)
+ plan: EvolutionPlan | None = None
diff --git a/agents/bee-evolver/src/hive/transformer/__init__.py b/agents/bee-evolver/src/hive/transformer/__init__.py
new file mode 100644
index 00000000..640d28dd
--- /dev/null
+++ b/agents/bee-evolver/src/hive/transformer/__init__.py
@@ -0,0 +1,184 @@
+import json
+from pathlib import Path
+from typing import Any
+
+import litellm
+import structlog
+
+from config import EvolverSettings
+from aura_core import find_hive_root
+from ..models import EvolutionPlan, Improvement, HiveContext
+
+logger = structlog.get_logger(__name__)
+
+
+class EvolverTransformer:
+ """T - Transformer: Analyzes Hive context and produces an EvolutionPlan."""
+
+ def __init__(self, settings: EvolverSettings) -> None:
+ self.settings = settings
+ self.model = settings.llm__model
+ litellm.api_key = settings.llm__api_key
+
+ root: Path = find_hive_root()
+ prompt_path = root / "agents/bee-evolver/prompts/bee_evolver.md"
+ self.persona = (
+ prompt_path.read_text()
+ if prompt_path.exists()
+ else "You are bee.Evolver, the evolutionary engine of the Aura Hive."
+ )
+
+ async def think(self, context: HiveContext) -> EvolutionPlan:
+ logger.info("evolver_transformer_think_started")
+ prompt = self._build_prompt(context)
+
+ try:
+ plan, tokens = await self._call_llm(prompt)
+ except Exception as e:
+ logger.warning("primary_llm_failed_trying_fallback", error=str(e))
+ try:
+ plan, tokens = await self._call_llm(prompt, use_fallback=True)
+ except Exception as fe:
+ logger.error("evolver_transformer_llm_failed", error=str(fe))
+ return EvolutionPlan(
+ narrative=(
+ "The Evolver's brain is offline. "
+ f"Primary: {e}. Fallback: {fe}"
+ ),
+ token_usage=0,
+ )
+
+ logger.info(
+ "evolver_transformer_think_done",
+ improvements=len(plan.improvements),
+ tokens=plan.token_usage,
+ )
+ return plan
+
+ # ------------------------------------------------------------------
+ # Private helpers
+ # ------------------------------------------------------------------
+
+ def _build_prompt(self, ctx: HiveContext) -> str:
+ issues_text = "\n".join(
+ f"- #{i['number']}: {i['title']}" for i in ctx.open_issues[:10]
+ )
+ prs_text = "\n".join(
+ f"- #{p['number']}: {p['title']} ({p['head']})" for p in ctx.open_prs[:5]
+ )
+ heresies_text = "\n".join(f"- {h}" for h in ctx.recent_heresies)
+ focus_section = (
+ f"\n### Focus Hint\n{ctx.focus_hint}\n" if ctx.focus_hint else ""
+ )
+
+ return f"""{self.persona}
+
+---
+
+## Current Hive State
+
+### Recent Commits (last 20)
+{ctx.git_log or "No commits found."}
+
+### HIVE_STATE.md (truncated to 3000 chars)
+{ctx.hive_state[:3000]}
+
+### Open GitHub Issues
+{issues_text or "None."}
+
+### Open Pull Requests
+{prs_text or "None."}
+
+### Filesystem (top-level)
+{chr(10).join(ctx.filesystem_map)}
+
+### bee.Keeper Persona Prompt (current)
+{ctx.keeper_prompt[:1500]}
+
+### Recent Heresies Detected by bee.Keeper
+{heresies_text or "None detected recently."}
+{focus_section}
+---
+
+## Task
+
+Analyze the Hive's current state and identify the top {self.settings.max_improvements} highest-value improvements.
+
+For each improvement, choose one of these types:
+- `code`: a concrete code change (provide a unified diff patch)
+- `prompt`: an update to a prompt file (provide full updated file content as patch)
+- `doc`: an update to a markdown doc section (provide the updated section as patch)
+- `issue`: a well-scoped GitHub Issue for a larger task (no patch needed)
+
+Return a JSON object with this exact schema:
+{{
+ "improvements": [
+ {{
+ "type": "code" | "prompt" | "doc" | "issue",
+ "title": "short title",
+ "description": "1-2 sentence description of what and why",
+ "target_file": "relative/path/to/file or null",
+ "patch": "unified diff or full file content or null",
+ "issue_body": "markdown body for GitHub Issue or null"
+ }}
+ ],
+ "narrative": "1-2 sentence metabolic status summary for Telegram",
+ "hive_is_optimal": false
+}}
+
+If the Hive is already in excellent shape and no improvements are warranted, return:
+{{
+ "improvements": [],
+ "narrative": "The Hive is crystalline. No mutations required.",
+ "hive_is_optimal": true
+}}
+
+Rules:
+- Patches must be valid unified diffs (--- a/file\\n+++ b/file\\n@@ ... @@) or full file replacements.
+- Issue bodies must be actionable markdown with clear acceptance criteria.
+- Do not invent files that don't exist in the filesystem map.
+- Prioritize improvements that fix existing heresies or open issues.
+- Keep patches minimal and focused.
+"""
+
+ async def _call_llm(
+ self, prompt: str, use_fallback: bool = False
+ ) -> tuple[EvolutionPlan, int]:
+ model = self.settings.llm__fallback_model if use_fallback else self.model
+ kwargs: dict[str, Any] = {
+ "model": model,
+ "messages": [{"role": "user", "content": prompt}],
+ "response_format": {"type": "json_object"},
+ "max_tokens": self.settings.max_tokens,
+ "timeout": 60.0,
+ "api_key": self.settings.llm__api_key,
+ }
+ if use_fallback and "ollama" in model:
+ kwargs["api_base"] = self.settings.llm__ollama_base_url
+
+ response = await litellm.acompletion(**kwargs)
+ content = response.choices[0].message.content or "{}"
+ tokens = 0
+ if hasattr(response, "usage") and response.usage:
+ tokens = getattr(response.usage, "total_tokens", 0)
+
+ data: dict[str, Any] = json.loads(content)
+ improvements = [
+ Improvement(
+ type=item.get("type", "issue"),
+ title=item.get("title", ""),
+ description=item.get("description", ""),
+ target_file=item.get("target_file"),
+ patch=item.get("patch"),
+ issue_body=item.get("issue_body"),
+ )
+ for item in data.get("improvements", [])
+ ]
+
+ plan = EvolutionPlan(
+ improvements=improvements,
+ narrative=data.get("narrative", ""),
+ token_usage=tokens,
+ hive_is_optimal=bool(data.get("hive_is_optimal", False)),
+ )
+ return plan, tokens
diff --git a/hive-manifest.yaml b/hive-manifest.yaml
index 265a95dc..45506477 100644
--- a/hive-manifest.yaml
+++ b/hive-manifest.yaml
@@ -58,6 +58,8 @@ allowed_chambers:
agents: WorkerCells
agents/bee-keeper: KeeperCell
agents/bee-keeper/src/hive/: KeeperNucleus
+ agents/bee-evolver: EvolverCell
+ agents/bee-evolver/src/hive/: EvolverNucleus
core/src/hive/aggregator/db.py: AuthorizedEnzyme
core/src/hive/aggregator/embeddings.py: AuthorizedEnzyme
core/src/hive/aggregator/main.py: AuthorizedEnzyme
diff --git a/pyproject.toml b/pyproject.toml
index 39bd510b..269ff505 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -86,6 +86,7 @@ exclude = [
"**/proto/**", # Auto-generated protobuf files
"**/gen/**", # Auto-generated dataclass files
"**/gen-proto/**", # Auto-generated proto files
+ "tools/evolver_colab.ipynb", # Colab notebook — cross-cell state makes E402/I001 inapplicable
]
[tool.ruff.lint]
diff --git a/tools/evolver_colab.ipynb b/tools/evolver_colab.ipynb
new file mode 100644
index 00000000..29c22f88
--- /dev/null
+++ b/tools/evolver_colab.ipynb
@@ -0,0 +1,227 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 5,
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "version": "3.12.0"
+ },
+ "colab": {
+ "provenance": [],
+ "gpuType": "T4"
+ },
+ "accelerator": "GPU"
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "intro",
+ "metadata": {},
+ "source": [
+ "# 🧬 bee.Evolver — Colab Runner\n",
+ "\n",
+ "Runs the Aura Hive evolutionary cycle on Google Colab's free T4 GPU.\n",
+ "Use this for deeper analysis with larger models when the weekly GitHub Actions cycle isn't enough.\n",
+ "\n",
+ "**Before running:**\n",
+ "1. Set your secrets in the *Secrets* panel (🔑 icon in the left sidebar)\n",
+ "2. Required: `MISTRAL_API_KEY`, `GITHUB_TOKEN`, `GITHUB_REPOSITORY`, `AURA_TELEGRAM_TOKEN`, `AURA_BEE_KEEPER__ADMIN_CHAT_ID`\n",
+ "3. Optional: `EVOLVER_FOCUS` — free-text hint (e.g. `\"improve bee.Keeper prompts\"`)\n",
+ "4. Run all cells in order (Runtime → Run all)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "install",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cell 1: Install dependencies\n",
+ "!pip install -q litellm httpx gitpython pydantic-settings structlog"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "clone",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cell 2: Clone the Hive and configure git # noqa: E402, I001\n",
+ "import os # noqa: E402\n",
+ "import sys # noqa: E402\n",
+ "from google.colab import userdata # noqa: E402\n",
+ "\n",
+ "GITHUB_TOKEN = userdata.get('GITHUB_TOKEN')\n",
+ "GITHUB_REPOSITORY = userdata.get('GITHUB_REPOSITORY') # e.g. 'zaebee/aura'\n",
+ "\n",
+ "repo_url = f\"https://{GITHUB_TOKEN}@github.com/{GITHUB_REPOSITORY}.git\"\n",
+ "repo_dir = \"/content/aura\"\n",
+ "\n",
+ "if not os.path.exists(repo_dir):\n",
+ " !git clone {repo_url} {repo_dir}\n",
+ "else:\n",
+ " !git -C {repo_dir} pull\n",
+ "\n",
+ "!git -C {repo_dir} config user.name \"bee.Evolver\"\n",
+ "!git -C {repo_dir} config user.email \"evolver@aura.hive\"\n",
+ "\n",
+ "sys.path.insert(0, f\"{repo_dir}/agents/bee-evolver/src\")\n",
+ "sys.path.insert(0, f\"{repo_dir}/agents/bee-evolver\")\n",
+ "\n",
+ "print(f\"Hive cloned to {repo_dir}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "env_setup",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cell 3: Set environment variables from Colab secrets # noqa: E402, I001\n",
+ "from google.colab import userdata # noqa: E402, F811\n",
+ "\n",
+ "def _get_secret(key, default=''):\n",
+ " try:\n",
+ " return userdata.get(key) or default\n",
+ " except Exception:\n",
+ " return default\n",
+ "\n",
+ "os.environ['AURA_LLM__API_KEY'] = _get_secret('MISTRAL_API_KEY')\n",
+ "os.environ['AURA_LLM__MODEL'] = 'mistral/mistral-large-latest'\n",
+ "os.environ['AURA_LLM__FALLBACK_MODEL'] = 'openai/gpt-4o-mini'\n",
+ "os.environ['GITHUB_TOKEN'] = _get_secret('GITHUB_TOKEN')\n",
+ "os.environ['GITHUB_REPOSITORY'] = _get_secret('GITHUB_REPOSITORY')\n",
+ "os.environ['GITHUB_EVENT_NAME'] = 'manual'\n",
+ "os.environ['AURA_TELEGRAM_TOKEN'] = _get_secret('AURA_TELEGRAM_TOKEN')\n",
+ "os.environ['AURA_BEE_KEEPER__ADMIN_CHAT_ID'] = _get_secret('AURA_BEE_KEEPER__ADMIN_CHAT_ID', '0')\n",
+ "os.environ['EVOLVER_FOCUS'] = _get_secret('EVOLVER_FOCUS', '')\n",
+ "os.environ['EVOLVER_MAX_IMPROVEMENTS'] = '5'\n",
+ "os.environ['AURA_BEE_EVOLVER__MAX_TOKENS'] = '4000'\n",
+ "os.environ['EVOLVER_ASSIGNEE'] = 'zaebee'\n",
+ "\n",
+ "print(\"Environment configured.\")\n",
+ "print(f\" Model: {os.environ['AURA_LLM__MODEL']}\")\n",
+ "print(f\" Repository: {os.environ['GITHUB_REPOSITORY']}\")\n",
+ "print(f\" Focus: {os.environ.get('EVOLVER_FOCUS') or '(none)'}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "run_aggregator",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cell 4: Run Aggregator — sense the Hive # noqa: E402, I001\n",
+ "import asyncio # noqa: E402\n",
+ "import aura_core # noqa: E402\n",
+ "from pathlib import Path # noqa: E402\n",
+ "from config import EvolverSettings # noqa: E402\n",
+ "from hive.aggregator import EvolverAggregator # noqa: E402\n",
+ "\n",
+ "# Patch find_hive_root to point at the cloned repo\n",
+ "aura_core._HIVE_ROOT = Path(repo_dir) # type: ignore\n",
+ "\n",
+ "settings = EvolverSettings()\n",
+ "aggregator = EvolverAggregator(settings)\n",
+ "context = asyncio.get_event_loop().run_until_complete(aggregator.perceive())\n",
+ "\n",
+ "print(f\"Git log lines: {len(context.git_log.splitlines())}\")\n",
+ "print(f\"Open issues: {len(context.open_issues)}\")\n",
+ "print(f\"Open PRs: {len(context.open_prs)}\")\n",
+ "print(f\"Recent heresies: {len(context.recent_heresies)}\")\n",
+ "print(f\"Focus hint: {context.focus_hint or '(none)'}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "run_transformer",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cell 5: Run Transformer — generate EvolutionPlan # noqa: E402, I001\n",
+ "from hive.transformer import EvolverTransformer # noqa: E402\n",
+ "\n",
+ "transformer = EvolverTransformer(settings)\n",
+ "plan = asyncio.get_event_loop().run_until_complete(transformer.think(context))\n",
+ "\n",
+ "print(f\"Improvements: {len(plan.improvements)}\")\n",
+ "print(f\"Tokens used: {plan.token_usage}\")\n",
+ "print(f\"Optimal: {plan.hive_is_optimal}\")\n",
+ "print(f\"Narrative: {plan.narrative}\")\n",
+ "print()\n",
+ "for i, imp in enumerate(plan.improvements, 1):\n",
+ " print(f\"{i}. [{imp.type}] {imp.title}\")\n",
+ " print(f\" {imp.description}\")\n",
+ " print(f\" target_file: {imp.target_file}\")\n",
+ " print()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "inspect_patches",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cell 6: Inspect patches before applying (optional review step)\n",
+ "for imp in plan.improvements:\n",
+ " if imp.patch:\n",
+ " print(f\"=== [{imp.type}] {imp.title} ===\")\n",
+ " print(f\"Target: {imp.target_file}\")\n",
+ " print(imp.patch[:1000])\n",
+ " print()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "apply_and_push",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Cell 7: Apply patches, push branch, open PR + Issues, send Telegram pulse\n",
+ "# ⚠️ This cell makes real changes to the repository. Review Cell 6 first.\n",
+ "# noqa: E402, I001\n",
+ "from hive.metabolism import EvolverMetabolism # noqa: E402\n",
+ "\n",
+ "metabolism = EvolverMetabolism(settings)\n",
+ "# Inject the already-computed plan to skip re-running the LLM\n",
+ "metabolism._precomputed_plan = plan # type: ignore\n",
+ "\n",
+ "observation = asyncio.get_event_loop().run_until_complete(metabolism.execute())\n",
+ "\n",
+ "print(f\"Success: {observation.success}\")\n",
+ "print(f\"PR URL: {observation.pr_url or '(none)'}\")\n",
+ "print(f\"Issues created: {len(observation.issue_urls)}\")\n",
+ "print(f\"Telegram sent: {observation.telegram_sent}\")\n",
+ "if observation.errors:\n",
+ " print(\"Errors:\")\n",
+ " for e in observation.errors:\n",
+ " print(f\" - {e}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "footer",
+ "metadata": {},
+ "source": [
+ "## Done\n",
+ "\n",
+ "If a PR was opened, review it at the URL above before merging.\n",
+ "All commits include `[skip ci]` to prevent CI loops.\n",
+ "\n",
+ "_🐝 For the glory of the Hive._"
+ ]
+ }
+ ]
+}