mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-27 01:11:40 +00:00
feat: add self-evolution plugin — agent self-optimization system
Add a comprehensive self-evolution system that enables Hermes Agent to continuously improve through automated analysis and optimization: Core components: - reflection_engine: Nightly session analysis (1:00 AM) - evolution_proposer: Generate improvement proposals from insights - quality_scorer: Multi-dimensional session quality evaluation - strategy_injector: Inject learned strategies into new sessions - strategy_compressor: Strategy optimization and deduplication - git_analyzer: Code change pattern analysis - rule_engine: Pattern-based rule generation - feishu_notifier: Feishu card notifications for evolution events Storage: - db.py: SQLite telemetry storage - strategy_store: Persistent strategy storage - models.py: Data models Plugin integration: - plugin.yaml, hooks.py, __init__.py for plugin system - cron_jobs.py for scheduled tasks Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e5d41f05d4
commit
3cd384dc43
23 changed files with 6173 additions and 0 deletions
124
self_evolution/strategy_injector.py
Normal file
124
self_evolution/strategy_injector.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""
|
||||
Self Evolution Plugin — Strategy Injector
|
||||
===========================================
|
||||
|
||||
Injects learned strategy hints into sessions via pre_llm_call hook.
|
||||
|
||||
Design reference: Claude Code plugins/learning-output-style/
|
||||
- SessionStart hook injects behavioral context automatically
|
||||
- Equivalent to CLAUDE.md but more flexible and distributable
|
||||
- No core modification needed
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from self_evolution.models import StrategyRule
|
||||
from self_evolution.rule_engine import StrategyRuleEngine
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_engine = StrategyRuleEngine()
|
||||
|
||||
# ── TTL-based cache to avoid reading strategies.json on every LLM call ────
|
||||
|
||||
_cached_strategies: list | None = None
|
||||
_cache_ts: float = 0.0
|
||||
_CACHE_TTL: float = 60.0 # seconds
|
||||
|
||||
|
||||
def _load_active_strategies() -> list:
|
||||
"""Load active strategies from strategy store (cached for _CACHE_TTL)."""
|
||||
global _cached_strategies, _cache_ts
|
||||
|
||||
now = time.time()
|
||||
if _cached_strategies is not None and (now - _cache_ts) < _CACHE_TTL:
|
||||
return _cached_strategies
|
||||
|
||||
from self_evolution.strategy_store import StrategyStore
|
||||
|
||||
store = StrategyStore()
|
||||
data = store.load()
|
||||
rules = data.get("rules", [])
|
||||
|
||||
strategies = []
|
||||
for rule_data in rules:
|
||||
if not rule_data.get("enabled", True):
|
||||
continue
|
||||
strategy = StrategyRule.from_dict(rule_data)
|
||||
strategies.append(strategy)
|
||||
|
||||
_cached_strategies = strategies
|
||||
_cache_ts = now
|
||||
return strategies
|
||||
|
||||
|
||||
def invalidate_cache():
|
||||
"""Invalidate the strategy cache (call after strategy updates)."""
|
||||
global _cached_strategies
|
||||
_cached_strategies = None
|
||||
|
||||
|
||||
_MAX_INJECT_STRATEGIES = 3 # 最多注入策略数
|
||||
_MAX_HINT_CHARS = 100 # 注入提示总字符预算
|
||||
_MAX_SINGLE_HINT = 30 # 单条 hint_text 最大字符数
|
||||
|
||||
def inject_hints(kwargs: dict) -> Optional[str]:
|
||||
"""Pre-llm-call hook: inject learned strategy hints.
|
||||
|
||||
Rules:
|
||||
- Strategies without conditions are skipped (must be condition-based).
|
||||
- hint_text longer than _MAX_SINGLE_HINT chars are skipped.
|
||||
- At most _MAX_INJECT_STRATEGIES hints, total ≤ _MAX_HINT_CHARS.
|
||||
"""
|
||||
strategies = _load_active_strategies()
|
||||
if not strategies:
|
||||
return None
|
||||
|
||||
# Build context from current session
|
||||
context = _build_context(kwargs)
|
||||
|
||||
# Match strategies
|
||||
matched = _engine.match_strategies(strategies, context)
|
||||
if not matched:
|
||||
return None
|
||||
|
||||
# Filter: require conditions and enforce hint length
|
||||
eligible = []
|
||||
for s in matched:
|
||||
if not s.conditions:
|
||||
continue # Skip unconditioned strategies
|
||||
if len(s.hint_text.strip()) > _MAX_SINGLE_HINT:
|
||||
continue # Skip overly long hints
|
||||
eligible.append(s)
|
||||
|
||||
if not eligible:
|
||||
return None
|
||||
|
||||
# Deduplicate by hint_text content
|
||||
seen_hints: set[str] = set()
|
||||
unique: list = []
|
||||
for s in eligible:
|
||||
key = s.hint_text.strip().lower()
|
||||
if key not in seen_hints:
|
||||
seen_hints.add(key)
|
||||
unique.append(s)
|
||||
|
||||
# Cap count
|
||||
selected = unique[:_MAX_INJECT_STRATEGIES]
|
||||
|
||||
# Format hints within char budget
|
||||
return _engine.format_hints(selected, max_chars=_MAX_HINT_CHARS)
|
||||
|
||||
|
||||
def _build_context(kwargs: dict) -> dict:
|
||||
"""Build matching context from hook kwargs."""
|
||||
return {
|
||||
"platform": kwargs.get("platform", ""),
|
||||
"model": kwargs.get("model", ""),
|
||||
"task_type": kwargs.get("task_type", ""),
|
||||
"tool_name": kwargs.get("tool_name", ""),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue