mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 01:21:43 +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
141
self_evolution/strategy_compressor.py
Normal file
141
self_evolution/strategy_compressor.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
"""
|
||||
Self Evolution Plugin — Strategy Compressor
|
||||
=============================================
|
||||
|
||||
Compresses and merges redundant strategy rules into concise hints.
|
||||
|
||||
Called after dream consolidation to keep strategies.json compact.
|
||||
Each hint_text must be ≤ 30 chars; strategies without conditions are
|
||||
either merged into conditional rules or discarded.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, List
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Maximum allowed length for hint_text (characters)
|
||||
MAX_HINT_LENGTH = 30
|
||||
|
||||
# Keyword clusters used to group similar strategies
|
||||
_CLUSTERS: List[Dict[str, Any]] = [
|
||||
{
|
||||
"keywords": ["bash", "路径", "path", "校验", "预检", "验证", "存在"],
|
||||
"hint": "bash前先read验证路径",
|
||||
"condition": {"field": "tool_name", "operator": "contains", "pattern": "bash"},
|
||||
},
|
||||
{
|
||||
"keywords": ["api", "调试", "debug", "降级", "只读", "探查"],
|
||||
"hint": "API失败时降级只读探查",
|
||||
"condition": {"field": "task_type", "operator": "contains", "pattern": "api"},
|
||||
},
|
||||
{
|
||||
"keywords": ["browser", "浏览器", "timeout", "超时", "网页"],
|
||||
"hint": "浏览器操作设置超时保护",
|
||||
"condition": {"field": "tool_name", "operator": "contains", "pattern": "browser"},
|
||||
},
|
||||
{
|
||||
"keywords": ["重试", "retry", "浪费", "重复", "循环"],
|
||||
"hint": "避免重复重试相同操作",
|
||||
"condition": {},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def compress_strategies(rules: List[dict]) -> List[dict]:
|
||||
"""Compress strategy rules by merging similar ones.
|
||||
|
||||
Returns a new list of rules with:
|
||||
- Duplicate hint_texts removed
|
||||
- Similar rules merged into cluster summaries
|
||||
- hint_text truncated to MAX_HINT_LENGTH
|
||||
- Non-matching rules dropped if they have no conditions
|
||||
"""
|
||||
if not rules:
|
||||
return []
|
||||
|
||||
# Deduplicate by hint_text
|
||||
seen_hints: set[str] = set()
|
||||
unique: list[dict] = []
|
||||
for r in rules:
|
||||
key = r.get("hint_text", "").strip().lower()
|
||||
if key and key not in seen_hints:
|
||||
seen_hints.add(key)
|
||||
unique.append(r)
|
||||
|
||||
# Cluster similar rules
|
||||
clustered = _cluster_rules(unique)
|
||||
|
||||
# Enforce constraints: hint_text ≤ 30 chars, must have conditions
|
||||
result: list[dict] = []
|
||||
for r in clustered:
|
||||
hint = r.get("hint_text", "").strip()
|
||||
conditions = r.get("conditions", [])
|
||||
|
||||
# Skip rules without conditions (they won't be injected anyway)
|
||||
if not conditions:
|
||||
logger.debug("Dropping unconditioned strategy: %s", hint[:40])
|
||||
continue
|
||||
|
||||
# Truncate hint if needed
|
||||
if len(hint) > MAX_HINT_LENGTH:
|
||||
hint = hint[:MAX_HINT_LENGTH]
|
||||
r["hint_text"] = hint
|
||||
|
||||
result.append(r)
|
||||
|
||||
# Also keep any manual/default rules that already have conditions
|
||||
for r in unique:
|
||||
if r.get("source") in ("manual", "default") and r.get("conditions"):
|
||||
if r not in result:
|
||||
hint = r.get("hint_text", "").strip()
|
||||
if len(hint) > MAX_HINT_LENGTH:
|
||||
r["hint_text"] = hint[:MAX_HINT_LENGTH]
|
||||
result.append(r)
|
||||
|
||||
logger.info("Compressed strategies: %d → %d rules", len(rules), len(result))
|
||||
return result
|
||||
|
||||
|
||||
def _cluster_rules(rules: list[dict]) -> list[dict]:
|
||||
"""Group rules by keyword clusters and merge each group into one rule."""
|
||||
matched_indices: set[int] = set()
|
||||
merged: list[dict] = []
|
||||
|
||||
for cluster in _CLUSTERS:
|
||||
group: list[dict] = []
|
||||
for i, r in enumerate(rules):
|
||||
text = f"{r.get('name', '')} {r.get('hint_text', '')}".lower()
|
||||
if any(kw in text for kw in cluster["keywords"]):
|
||||
group.append(r)
|
||||
matched_indices.add(i)
|
||||
|
||||
if not group:
|
||||
continue
|
||||
|
||||
# Merge group into one rule
|
||||
first = group[0]
|
||||
condition = cluster.get("condition")
|
||||
merged_rule = {
|
||||
"id": first.get("id", ""),
|
||||
"name": cluster["hint"],
|
||||
"type": "learned",
|
||||
"description": cluster["hint"],
|
||||
"hint_text": cluster["hint"],
|
||||
"conditions": [condition] if condition else [],
|
||||
"severity": "medium",
|
||||
"enabled": True,
|
||||
"source": "learned",
|
||||
"created_at": first.get("created_at", 0),
|
||||
}
|
||||
merged.append(merged_rule)
|
||||
|
||||
# Add unmatched rules as-is
|
||||
for i, r in enumerate(rules):
|
||||
if i not in matched_indices:
|
||||
merged.append(r)
|
||||
|
||||
return merged
|
||||
Loading…
Add table
Add a link
Reference in a new issue