with one click
hermes-memory-migration
// Restore Hermes Agent memory and skills from a GitHub backup repository. Use when onboarding a new Hermes instance or recovering lost knowledge from a backup.
// Restore Hermes Agent memory and skills from a GitHub backup repository. Use when onboarding a new Hermes instance or recovering lost knowledge from a backup.
| name | hermes-memory-migration |
| description | Restore Hermes Agent memory and skills from a GitHub backup repository. Use when onboarding a new Hermes instance or recovering lost knowledge from a backup. |
| tags | ["hermes","memory","skills","migration","github","backup","restore"] |
.env file (e.g. GITHUB_HERMESSHIFT_PAT)HermesAgent/ structure:
HermesAgent/
├── memories/
│ ├── MEMORY.md
│ └── USER.md
└── skills/
└── skills/
└── <category>/<skill-name>/SKILL.md
When the user provides a local path like /root/aws.git/HermesAgent, skip all git/API steps entirely.
LOCAL_REPO="/root/aws.git/HermesAgent" # path given by user
# 1. Read memory files directly
cat $LOCAL_REPO/memories/MEMORY.md
cat $LOCAL_REPO/memories/USER.md
Then restore memory entries with memory(action='add', ...) calls.
For skills, use the Python selective-copy approach (copies only custom/non-bundled skills, skips already-existing ones):
import os, shutil
src_base = "/root/aws.git/HermesAgent/skills/skills"
dst_base = os.path.expanduser("~/.hermes/skills")
# Standard bundled skills already present in fresh Hermes — skip these
standard_skills = {
"autonomous-ai-agents": ["claude-code", "codex", "hermes-agent", "opencode"],
"creative": ["architecture-diagram", "ascii-art", "ascii-video", "baoyu-comic",
"baoyu-infographic", "claude-design", "comfyui", "design-md", "excalidraw",
"humanizer", "manim-video", "p5js", "pixel-art", "popular-web-designs",
"pretext", "sketch", "songwriting-and-ai-music", "touchdesigner-mcp"],
"data-science": ["jupyter-live-kernel"],
"devops": ["kanban-orchestrator", "kanban-worker", "webhook-subscriptions"],
"dogfood": ["dogfood"],
"email": ["himalaya"],
"gaming": ["minecraft-modpack-server", "pokemon-player"],
"github": ["codebase-inspection", "github-auth", "github-code-review", "github-issues",
"github-pr-workflow", "github-repo-management"],
"mcp": ["native-mcp"],
"media": ["gif-search", "heartmula", "songsee", "spotify", "youtube-content"],
"note-taking": ["obsidian"],
"productivity": ["airtable", "google-workspace", "linear", "maps", "nano-pdf", "notion",
"ocr-and-documents", "powerpoint"],
"red-teaming": ["godmode"],
"research": ["arxiv", "blogwatcher", "llm-wiki", "polymarket"],
"smart-home": ["openhue"],
"social-media": ["xurl"],
"software-development": ["debugging-hermes-tui-commands", "hermes-agent-skill-authoring",
"node-inspect-debugger", "plan", "python-debugpy",
"requesting-code-review", "spike", "subagent-driven-development",
"systematic-debugging", "test-driven-development", "writing-plans"],
"workspace-dispatch": ["workspace-dispatch"],
"yuanbao": ["yuanbao"],
}
copied, skipped = [], []
for category in os.listdir(src_base):
if category.startswith('.'): continue
cat_path = os.path.join(src_base, category)
if not os.path.isdir(cat_path): continue
dst_cat = os.path.join(dst_base, category)
os.makedirs(dst_cat, exist_ok=True)
standard = standard_skills.get(category, [])
for skill in os.listdir(cat_path):
if skill.startswith('.') or skill == 'DESCRIPTION.md': continue
src = os.path.join(cat_path, skill)
dst = os.path.join(dst_cat, skill)
if not os.path.isdir(src) or not os.path.exists(os.path.join(src, "SKILL.md")): continue
if skill not in standard:
if not os.path.exists(dst):
shutil.copytree(src, dst)
copied.append(f"{category}/{skill}")
else:
skipped.append(f"{category}/{skill}")
print("Copied:", copied)
print("Skipped (existing):", skipped)
This safely copies only user-created custom skills, leaving bundled skills untouched.
When restoring a full instance (memories + all skills), the git clone + shutil bulk copy approach is much faster than file-by-file GitHub API calls.
# 0. ⚠️ 必須:メモリ上限を拡張する(先に実行しないと記憶が全て入りきらない)
# デフォルト: memory_char_limit=2200, user_char_limit=1375 → 記憶が途中で切れる
hermes config set memory.memory_char_limit 8000
hermes config set memory.user_char_limit 4000
# 1. Clone the repo (uses HTTPS with PAT for auth)
source /root/.hermes/profiles/bedrock-slack/.env
git clone https://MITLabo:${GITHUB_HERMESSHIFT_PAT}@github.com/shiftrepo/aws.git /tmp/aws-rebuild
# 2. Read README.md first — it may contain critical constraints (e.g. AWS resource prohibitions)
cat /tmp/aws-rebuild/HermesAgent/README.md
# 3. Copy memories directly into the Hermes memories dir
# ⚠️ Correct path is /root/.hermes/memories/ — NOT /root/.hermes/profiles/bedrock-slack/memories/
cp /tmp/aws-rebuild/HermesAgent/memories/MEMORY.md /root/.hermes/memories/MEMORY.md
cp /tmp/aws-rebuild/HermesAgent/memories/USER.md /root/.hermes/memories/USER.md
# 4. Bulk-copy all skills (rsync, preserves nested structure)
# ⚠️ Correct path is /root/.hermes/skills/ — NOT /root/.hermes/profiles/bedrock-slack/skills/
rsync -av /tmp/aws-rebuild/HermesAgent/skills/skills/ /root/.hermes/skills/
Then load the memory files into the live agent with memory(action='add', ...) calls (see Step 3 below for handling capacity).
Note: After cp the files are updated on disk, but the agent's live in-context memory is separate. You must also call memory() to inject the restored entries into the session.
source /root/.hermes/profiles/bedrock-slack/.env
echo $GITHUB_HERMESSHIFT_PAT | head -c 10 # verify it's loaded
source /root/.hermes/profiles/bedrock-slack/.env
# MEMORY.md
curl -s -H "Authorization: token $GITHUB_HERMESSHIFT_PAT" \
"https://api.github.com/repos/<owner>/<repo>/contents/HermesAgent/memories/MEMORY.md" | \
python3 -c "import json,sys,base64; d=json.load(sys.stdin); print(base64.b64decode(d['content']).decode('utf-8'))"
# USER.md
curl -s -H "Authorization: token $GITHUB_HERMESSHIFT_PAT" \
"https://api.github.com/repos/<owner>/<repo>/contents/HermesAgent/memories/USER.md" | \
python3 -c "import json,sys,base64; d=json.load(sys.stdin); print(base64.b64decode(d['content']).decode('utf-8'))"
cat /root/.hermes/memories/MEMORY.mdcat /root/.hermes/memories/USER.mdmemory(action='add', ...) to restore only the missing entriesmemory(action='remove', ...) first, then add the merged compact versionsource /root/.hermes/profiles/bedrock-slack/.env
curl -s -H "Authorization: token $GITHUB_HERMESSHIFT_PAT" \
"https://api.github.com/repos/<owner>/<repo>/contents/HermesAgent/skills/skills" | \
python3 -c "import json,sys; [print(i['type'], i['name']) for i in json.load(sys.stdin)]"
# Check a category (e.g. devops)
curl -s -H "Authorization: token $GITHUB_HERMESSHIFT_PAT" \
"https://api.github.com/repos/<owner>/<repo>/contents/HermesAgent/skills/skills/devops" | \
python3 -c "import json,sys; [print(i['type'], i['name']) for i in json.load(sys.stdin)]"
ls /root/.hermes/skills/devops/
Important: Use terminal() with source .env to keep the token in scope. Do NOT pass the token via execute_code (token gets lost between calls).
source /root/.hermes/profiles/bedrock-slack/.env
# Fetch SKILL.md content
curl -s -H "Authorization: token $GITHUB_HERMESSHIFT_PAT" \
"https://api.github.com/repos/<owner>/<repo>/contents/HermesAgent/skills/skills/<category>/<skill>/SKILL.md" | \
python3 -c "import json,sys,base64; d=json.load(sys.stdin); print(base64.b64decode(d['content']).decode('utf-8'))"
# Save to local skills dir
mkdir -p /root/.hermes/profiles/bedrock-slack/skills/<category>/<skill>/
# Then use write_file() to save the content
ls /root/.hermes/profiles/bedrock-slack/skills/<category>/
cat /root/.hermes/profiles/bedrock-slack/skills/<category>/<skill>/SKILL.md
復元後に cronjob(action='list') を実行して確認し、必要であればユーザーと相談して再設定すること。
deliver: origin を使うこと(slack:CHANNEL_ID:THREAD_ID 形式は channel_not_found になる)0 20 * * *このジョブはUTC 20:00(JST 05:00)に実行されます。
「今日」とはJST基準(UTC+9時間)の今日を指します。
send_message を明示的にプロンプトに含めること:
レポート作成後、必ずsend_messageツールを使ってtarget="slack:CHANNEL_ID"に送信してください。
source .env inside terminal() calls within execute_code does NOT persist the env var across separate terminal() calls in the same script. Solution: use a single terminal() call that sources .env AND runs the curl in one command.json_parse() helper or process incrementally.hermes config set memory.memory_char_limit 8000 before restoring. The § separator in MEMORY.md marks entry boundaries.HermesAgent/skills/skills/ (double skills/) — verify the path depth before fetching.memory(action='add', target='user', ...) — it's separate from MEMORY.md./root/.hermes/memories/ ⚠️ NOT /root/.hermes/profiles/bedrock-slack/memories/ (that dir may exist but is empty/stale)/root/.hermes/skills/ ⚠️ NOT /root/.hermes/profiles/bedrock-slack/skills//root/.hermes/profiles/bedrock-slack/.envVerified 2026-05-02:
find /root/.hermes -name "MEMORY.md"returns/root/.hermes/memories/MEMORY.md. The profile-relative path is a red herring — always probe withfindfirst.
[HINT] Download the complete skill directory including SKILL.md and all related files