Skip to content

Commit d612125

Browse files
committed
v0.33.0: 5 performance improvements across the stack
- HTTP connection pooling: tuned transport with keep-alives for LLM + Telegram clients - Context trim O(n²) → O(n): running token total instead of re-scanning all messages - Session compact JSON: json.Marshal instead of json.MarshalIndent - Memory LLM cascade disabled: RP ranker by default (zero LLM calls per turn) - Persistent skill cache: survives across odek run invocations - Episode index cache: in-memory index avoids disk I/O on every loop turn
1 parent bbb005e commit d612125

15 files changed

Lines changed: 1037 additions & 19 deletions

File tree

convert_hermes_skills.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Convert all Hermes Agent skills to odek SKILL.md format.
4+
Handles nested directory structures like mlops/inference/llama-cpp/SKILL.md
5+
"""
6+
7+
import os
8+
import re
9+
import sys
10+
11+
HERMES_SKILLS_DIR = os.path.expanduser("~/.hermes/skills")
12+
ODEK_SKILLS_DIR = os.path.expanduser("~/.odek/skills")
13+
14+
15+
def parse_frontmatter(text):
16+
"""Parse YAML frontmatter between --- markers. Returns (dict, body)."""
17+
m = re.match(r'^---\s*\n(.*?)\n---\s*\n(.*)', text, re.DOTALL)
18+
if not m:
19+
return {}, text
20+
21+
raw = m.group(1)
22+
body = m.group(2)
23+
24+
data = {}
25+
current_key = None
26+
27+
for line in raw.split('\n'):
28+
m2 = re.match(r'^(\w[\w_-]*)\s*:\s*(.*)', line)
29+
if m2 and not line.startswith(' '):
30+
current_key = m2.group(1)
31+
val = m2.group(2).strip()
32+
val = re.sub(r'^["\'](.*)["\']$', r'\1', val)
33+
34+
if val == '' or val == '[]':
35+
data[current_key] = [] if val == '[]' else ''
36+
elif val.startswith('['):
37+
items = re.findall(r'["\']?(\w[\w\s.-]*)["\']?', val.strip('[]'))
38+
data[current_key] = [x.strip() for x in items if x.strip()]
39+
else:
40+
data[current_key] = val
41+
continue
42+
43+
m3 = re.match(r'^\s+(\w[\w_-]*)\s*:\s*(.*)', line)
44+
if m3 and current_key:
45+
sub_key = m3.group(1)
46+
sub_val = m3.group(2).strip()
47+
sub_val = re.sub(r'^["\'](.*)["\']$', r'\1', sub_val)
48+
if current_key not in data:
49+
data[current_key] = {}
50+
if isinstance(data.get(current_key), dict):
51+
data[current_key][sub_key] = sub_val
52+
continue
53+
54+
m4 = re.match(r'^\s+-\s+(.*)', line)
55+
if m4 and current_key:
56+
item = m4.group(1).strip()
57+
item = re.sub(r'^["\'](.*)["\']$', r'\1', item)
58+
if isinstance(data.get(current_key), list):
59+
data[current_key].append(item)
60+
continue
61+
62+
return data, body.strip()
63+
64+
65+
def generate_triggers(name, description, tags, category):
66+
"""Generate odek trigger topics and actions from Hermes metadata."""
67+
topics = set()
68+
69+
if isinstance(tags, list):
70+
for t in tags:
71+
t_clean = t.lower().strip()
72+
topics.add(t_clean)
73+
for part in re.split(r'[,/\s-]+', t_clean):
74+
if len(part) > 1:
75+
topics.add(part)
76+
77+
for part in name.replace('-', ' ').replace('_', ' ').split():
78+
if len(part) > 2:
79+
topics.add(part.lower())
80+
81+
topics.add(category.lower())
82+
83+
actions = set()
84+
desc_lower = description.lower()
85+
86+
action_verbs = [
87+
'build', 'create', 'deploy', 'develop', 'test', 'run', 'debug',
88+
'analyze', 'audit', 'research', 'write', 'configure', 'setup',
89+
'install', 'manage', 'monitor', 'generate', 'convert', 'design',
90+
'search', 'integrate', 'optimize', 'refactor', 'review', 'fix',
91+
'document', 'plan', 'orchestrate', 'simulate', 'train'
92+
]
93+
94+
for verb in action_verbs:
95+
if verb in desc_lower:
96+
actions.add(verb)
97+
98+
name_lower = name.lower()
99+
name_action_map = {
100+
'development': 'develop', 'testing': 'test', 'deployment': 'deploy',
101+
'debugging': 'debug', 'monitoring': 'monitor', 'audit': 'audit',
102+
'research': 'research', 'search': 'search', 'training': 'train',
103+
'generation': 'generate', 'management': 'manage', 'planning': 'plan',
104+
'analysis': 'analyze', 'review': 'review',
105+
}
106+
for key, verb in name_action_map.items():
107+
if key in name_lower:
108+
actions.add(verb)
109+
110+
if not actions:
111+
actions.add('use')
112+
113+
return ' '.join(sorted(topics)), ' '.join(sorted(actions))
114+
115+
116+
def convert_skill(skill_path, category):
117+
"""Convert one Hermes SKILL.md to odek format."""
118+
with open(skill_path, 'r') as f:
119+
content = f.read()
120+
121+
front, body = parse_frontmatter(content)
122+
123+
name = front.get('name', os.path.basename(os.path.dirname(skill_path)))
124+
description = front.get('description', '')
125+
126+
tags = front.get('tags', [])
127+
if not tags and 'metadata' in front and isinstance(front['metadata'], dict):
128+
tags = front['metadata'].get('hermes', {}).get('tags', [])
129+
if not tags:
130+
tags = [category, name]
131+
132+
topic, action = generate_triggers(name, description, tags, category)
133+
134+
odek_front = f"""---
135+
name: {name}
136+
description: {description}
137+
odek:
138+
trigger:
139+
topic: {topic}
140+
action: {action}
141+
auto_load: false
142+
quality: stable
143+
---
144+
145+
"""
146+
147+
body = body.strip()
148+
if not body.startswith('# '):
149+
heading = name.replace('-', ' ').replace('_', ' ').title()
150+
body = f"# {heading}\n\n{body}"
151+
152+
full_content = odek_front + body
153+
154+
skill_dir = os.path.join(ODEK_SKILLS_DIR, name)
155+
os.makedirs(skill_dir, exist_ok=True)
156+
157+
out_path = os.path.join(skill_dir, 'SKILL.md')
158+
with open(out_path, 'w') as f:
159+
f.write(full_content)
160+
161+
return name
162+
163+
164+
def find_all_skills(base_dir):
165+
"""Find all SKILL.md files at any nesting depth."""
166+
skills = []
167+
for root, dirs, files in os.walk(base_dir):
168+
if 'SKILL.md' in files:
169+
# Determine category from relative path
170+
rel = os.path.relpath(root, base_dir)
171+
parts = rel.split(os.sep)
172+
173+
if len(parts) == 1:
174+
# Top-level skill: skills/name
175+
category = 'uncategorized'
176+
skill_name = parts[0]
177+
elif len(parts) == 2:
178+
# Normal: skills/category/name
179+
category = parts[0]
180+
skill_name = parts[1]
181+
else:
182+
# Deeply nested: skills/category/subcategory/name
183+
category = parts[0]
184+
skill_name = parts[-1]
185+
186+
skill_path = os.path.join(root, 'SKILL.md')
187+
skills.append((skill_path, category, skill_name))
188+
189+
return skills
190+
191+
192+
def main():
193+
print(f"🔍 Scanning {HERMES_SKILLS_DIR} for Hermes skills...")
194+
195+
skills_found = find_all_skills(HERMES_SKILLS_DIR)
196+
197+
print(f"📦 Found {len(skills_found)} Hermes skills to convert")
198+
print()
199+
200+
converted = 0
201+
errors = 0
202+
skipped = 0
203+
204+
for skill_path, category, skill_name in skills_found:
205+
odek_path = os.path.join(ODEK_SKILLS_DIR, skill_name, 'SKILL.md')
206+
if os.path.isfile(odek_path):
207+
print(f" ⏭️ {skill_name} (already exists)")
208+
skipped += 1
209+
continue
210+
211+
try:
212+
converted_name = convert_skill(skill_path, category)
213+
print(f" ✅ {converted_name}")
214+
converted += 1
215+
except Exception as e:
216+
print(f" ❌ {skill_name}: {e}")
217+
errors += 1
218+
219+
print()
220+
print(f"─── Summary ───")
221+
print(f" ✅ Converted: {converted}")
222+
print(f" ⏭️ Skipped: {skipped} (already exist)")
223+
print(f" ❌ Errors: {errors}")
224+
print(f" 📊 Total processed: {len(skills_found)}")
225+
226+
227+
if __name__ == '__main__':
228+
main()

0 commit comments

Comments
 (0)