Install
npx skillscat add russ534/myopenclawbot/hermes-cross-platform-sync Install via the SkillsCat registry.
Hermes Cross-Platform Session Sync (sync-digest pattern)
Hermes runs independent sessions per endpoint (WeChat, Telegram, CLI). Each session loads the same /opt/data/AGENTS.md at boot — that file is the single shared context bridge. This skill is the canonical setup for keeping that bridge populated from a rolling digest of the last few hours of cross-endpoint traffic.
When to use this
- User complains the same info is lost between WeChat ↔ Telegram ↔ CLI (the most common pain point after multi-endpoint onboarding)
- User wants "shared memory" / "session hand-off" between endpoints
- User explicitly asks for cross-endpoint context sync
- User runs Hermes on more than one platform simultaneously and is doing related work across them
When NOT to use this
- Single-platform usage —
AGENTS.mdis enough - User wants per-user (not per-platform) memory → use the standard
memorytool, not this script - User wants per-task kanban hand-off →
kanban-orchestrator/kanban-worker, not this
The pattern in 30 seconds
- Schedule a script-mode cron (zero LLM tokens) that every N minutes:
- Reads
state.dbfor sessions withsource IN (weixin, telegram, cli)whoselast_activity_at >= now - WINDOW_HOURS - Pulls the last K
messagesrows for each session, paireduser → assistant - Filters noise (system / OOB /
[interrupt: ...]/Your request was not processed) - Renders a markdown digest
- Reads
- Write a "verbose" digest to
/opt/data/shared-context/DIGEST.md(always, full transcript, user-facing, prevents "you can't remember") - Inject a "compact" block into
/opt/data/AGENTS.mdbetween two sentinel comments — every new session auto-loads this via Hermes's standard AGENTS.md boot path - New session opens → already knows what the other endpoints discussed in the last few hours → no more "Telegram bot doesn't know what WeChat bot said"
Working setup (verified 2026-10-19)
The reference script at /opt/data/scripts/sync-digest.py (creator: user) implements the pattern. Key parameters — DO NOT change without re-reading what the user actually wants:
# All "magic numbers" — change ONLY after testing the user's stated goals
WINDOW_HOURS = 6.0 # verbose digest window
COMPACT_WINDOW_HOURS = 3.0 # AGENTS.md injection window
MSGS_PER_SESSION = 14 # per-endpoint, verbose digest
COMPACT_MSGS = 6 # per-endpoint, compact
MAX_CHARS_PER_MSG = 220 # per-message truncation
COMPACT_CHARS = 130 # per-message, compact
MAX_TOTAL_CHARS = 9000 # verbose digest size cap
COMPACT_MAX = 3500 # AGENTS.md block size cap
PLATFORMS = ("weixin", "telegram", "cli")
AGENTS_BEGIN = "<!-- CROSS-PLATFORM:BEGIN — 由 sync-digest.py 自动生成,勿手改 -->"
AGENTS_END = "<!-- CROSS-PLATFORM:END -->"
AGENTS_FILE = "/opt/data/AGENTS.md"
DIGEST_FILE = "/opt/data/shared-context/DIGEST.md"
STATE_DB = "/opt/data/state.db"
NOISE_PREFIXES = ("[OUT-OF-BAND USER MESSAGE", "Gateway message origin",
"Your request was not processed", "[System:")Pairing logic (critical): for each user message, keep the FIRST assistant reply that follows it. This produces a clean turn-aligned transcript instead of a noisy message dump.
Cron registration — use Hermes's own hermes cron (not system crontab), so the job is visible in the dashboard and auditable:
hermes cron add --script /opt/data/scripts/sync-digest.py \
--name "跨端记忆同步" \
--interval 5m \
--no-agent # ← critical: script cron, not LLM-drivenIf the user asks "every 10-20 minutes when conversation ends" — they're describing idle-trigger semantics, which Hermes's cron doesn't expose natively. Explain the trade-off and stay with interval 5m:
Cron is pull-based (every N min), not event-based (idle-detect). With a 5-min interval, the effective "delay before digest reflects the last message" is ≤5 min — functionally equivalent to "10-20 min after conversation ends" for any practical purpose, but without the cron-pulling-every-turn overhead that an idle-detect daemon would add.
If the user insists on true idle detection, the next-tier fallback is to write a long-polling watcher that hits state.db every 30s and watches last_activity_at per platform — but this is a 5-line loop on top of sync-digest.py, not a separate skill.
What the user said vs what they meant (precedent: this session)
| Phrase | Actual meaning |
|---|---|
| "对话结束 10-20 分钟无新对话就汇总" | "I don't want a digest mid-conversation; 5-min cron is fine" — interpret as interval, not "real-time" |
| "包括你的回复,防止你赖账" | Verbose DIGEST must include assistant turns (anti-gaslighting) |
| "汇总力度精简" | Compact AGENTS.md block — NOT verbose |
| "切换端口对话信息对齐" | AGENTS.md injection is the goal, DIGEST.md is the side-product |
| "创建你的几个分身" | Bad mental model — each endpoint shares the AGENTS.md bridge; no per-endpoint "分身" needed |
| "这个你自己定" | Stop offering options — pick the higher-confidence design, state the one-line trade-off, implement it, report what you did |
Outputs and where to find them
/opt/data/shared-context/DIGEST.md # verbose, full transcript, user-facing
/opt/data/AGENTS.md # compact block injected into every session
/opt/data/cron/jobs.json # cron registration entry
/opt/data/scripts/sync-digest.py # the script (do not edit without review)If DIGEST.md is stale (timestamp older than the cron interval), check hermes cron list and cat /opt/data/cron/output/ for execution errors — the script does not log to stderr on success, so a missing file means the job never ran, not that the script failed.
Why an already-open session still doesn't know (frozen system prompt)
The digest block and platform_hints are injected into the system prompt, which is built once per session and stored (sessions.system_prompt / system_prompts keyed by system_prompt_hash). Editing AGENTS.md, config.yaml:platform_hints, or restarting the gateway does not refresh a session that is already open — a WeChat/TG session started days ago keeps the old prompt indefinitely.
Consequence: after changing hints or the digest format, the endpoints the user has been using all day are exactly the ones that won't see the change. Say so, and ask for a /new on each endpoint (a new session rebuilds the prompt from the current files) — or push the facts straight into the live session (next section) when you don't want to interrupt it. Don't claim the rules are "now synced everywhere" after only restarting the gateway.
Pushing facts INTO another endpoint's live session (no /new needed)
The digest only lands at boot, and a live session never re-reads AGENTS.md. To make an already-open TG/WeChat session know something now, inject one turn into that session through the gateway's own api_server — it persists as a user row, the other-side agent answers, and the knowledge is in its transcript from then on.
import json, sqlite3, urllib.request
c = sqlite3.connect('/opt/data/state.db')
sid = c.execute("select id from sessions where source='telegram' order by started_at desc limit 1").fetchone()[0]
key = <API_SERVER_KEY from /opt/data/.env>
msg = "【容器终端同步】<facts>… 只回一句确认,不要执行任何命令。"
req = urllib.request.Request(
f"http://127.0.0.1:8642/api/sessions/{sid}/chat",
data=json.dumps({"message": msg}).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + key}, method="POST")
print(json.loads(urllib.request.urlopen(req, timeout=240).read())["message"]["content"])- Phrase it as "record this, don't act". It is a real agent turn with the full toolset — without that line the other endpoint may start executing the plan you only meant to hand over.
- Verify: the injected text shows up as a
userrow inmessagesfor that session, and the response'sruntime.modeltells you which model served it. - Delivery caveat: the reply comes back over HTTP and is not necessarily posted into the chat. If the user must see it on that endpoint, also send it through the platform's own API (Telegram
sendMessagevia the proxy; WeChat iLinksendmessagewith the savedcontext_token), or tell them to look at the session. - Use it when the other endpoint's prompt is frozen and the user won't send
/new; it closes the context gap without touching prompts or restarting anything.
Verify which sessions actually carry the rules:
import sqlite3
c = sqlite3.connect('/opt/data/state.db')
for sid, src, h in c.execute('select id, source, system_prompt_hash from sessions where source in ("weixin","telegram","cli") order by started_at desc limit 10'):
p = (c.execute('select prompt from system_prompts where hash=?', (h,)).fetchone() or [''])[0]
print(src, sid, 'hint=', '当前会话平台' in p, 'digest=', 'CROSS-PLATFORM' in p)hint=False on the session the user is actively using = the rule isn't live there yet, regardless of what the files say.
Git churn from the generated block
The CROSS-PLATFORM block is rewritten every run, so a repo that tracks AGENTS.md shows it as modified permanently. Never treat that M AGENTS.md as a real edit when deciding what to commit, and don't "fix" it by deleting the block.
Splitting the block into its own file and leaving a pointer in AGENTS.md does not work. Hermes auto-loads AGENTS.md itself and does not follow a reference to another file — the repo looks clean and the bridge is silently dead. Keep the block where it is and isolate the churn instead: an hourly script-mode cron that commits only AGENTS.md and pushes, staying quiet when nothing changed.
cronjob_manage(action="create", name="AGENTS.md 每小时自动提交",
schedule="every hour", script="autocommit-agents.py",
no_agent=True, deliver="local")Script rules: git add -- AGENTS.md (never -A, or unrelated edits ride along), fixed commit message, and a failed push is non-fatal (keep the local commit, next tick retries). Under no_agent, empty stdout already means "no notification", so the no-change path needs no special casing.
Proactive delivery: cron output → the user's phone
The gateway pushes, it doesn't only listen. Three per-job knobs: deliver (where output is posted), failure_deliver (where errors go), attach_to_session (whether the user can reply into the job).
- A bare platform target (
deliver="telegram","all") resolves at fire time to that platform's home channel, set by sending/sethomein that chat (persisted asTELEGRAM_HOME_CHANNEL/WEIXIN_HOME_CHANNELin/opt/data/.env)./sethomeis a chat-side command — it cannot be issued from the CLI, so moving the home channel stays a user action; state that instead of promising to do it yourself. - Pick the default by job shape: a one-line heartbeat (backup result, health summary) →
deliver="<platform>", one message a day is signal; a job that is silent unless something changed →deliver="local"+failure_deliver="<platform>", so only failures reach the phone. - A cron delivery leaves no
[Platform] Sending responseline ingateway.log. That line belongs to the agent-turn path; script-mode (no_agent) jobs go straight to the adapter. Log silence therefore proves nothing either way — checkhermes cron list(last-run status, delivery-error fields) and confirm the message actually landed on the device. When you cannot confirm it, say exactly that rather than reporting "pushed". - To exercise the pipe without waiting for the schedule:
cronjob_manage(action="run", job_id=…)fires it in the background and the outcome re-enters the conversation as an async result.
Verification after install
- Cron is scheduled:
hermes cron listshows the job,state: scheduled,enabled: true - Script runs:
python3 /opt/data/scripts/sync-digest.pyprintsdigest updated: N chars, platforms=[...](ordigest unchangedif no new traffic) - AGENTS.md has the block:
sed -n '/CROSS-PLATFORM:BEGIN/,/CROSS-PLATFORM:END/p' /opt/data/AGENTS.mdshows the compact 3-hour digest - New session sees it: open a new Telegram session, send a casual ping — the agent's first response should reference cross-endpoint context if relevant traffic exists in the window
Failure modes (for diagnosis, not prevention)
| Symptom | Cause | Fix |
|---|---|---|
digest unchanged every run |
state.db has no new messages in window | expected — message the user something new |
| AGENTS.md block is missing | sentinel comments were manually edited | restore markers (<!-- CROSS-PLATFORM:BEGIN --> ... <!-- CROSS-PLATFORM:END -->) |
| One endpoint missing | that endpoint's session source is not in PLATFORMS tuple |
edit PLATFORMS in script |
Cron job shows last_status: error |
python3 missing or state.db unreadable | python3 /opt/data/scripts/sync-digest.py runs manually to see traceback |
| digest is one-sided (only one platform) | user only used one endpoint in window | expected, not a bug |
What this skill is NOT
- Not a replacement for
hermes memorytool — that stores user-level facts across sessions; this stores per-platform recent context - Not a kanban / task-tracking system — that's
kanban-orchestrator - Not a multi-agent / sub-agent bus — Hermes's gateway already routes per-session; this just gives them a shared backdrop
Related skills
hermes-agent— for understanding Hermes session boot / AGENTS.md injection mechanismhermes-gateway-platform-diagnostics— for diagnosing "session won't start" / "channel silent" before assuming it's a sync issuekanban-orchestrator— when user wants task-level hand-off across sessions (not context-level)