diff --git a/public/banners/syncing-a-self-improving-ai-agent-across-machines.png b/public/banners/syncing-a-self-improving-ai-agent-across-machines.png
new file mode 100644
index 0000000..26b3e4a
Binary files /dev/null and b/public/banners/syncing-a-self-improving-ai-agent-across-machines.png differ
diff --git a/public/og/syncing-a-self-improving-ai-agent-across-machines.png b/public/og/syncing-a-self-improving-ai-agent-across-machines.png
new file mode 100644
index 0000000..7fe711d
Binary files /dev/null and b/public/og/syncing-a-self-improving-ai-agent-across-machines.png differ
diff --git a/scripts/banner-gen/generate.mjs b/scripts/banner-gen/generate.mjs
index ea18372..ec28c41 100644
--- a/scripts/banner-gen/generate.mjs
+++ b/scripts/banner-gen/generate.mjs
@@ -247,6 +247,26 @@ const BANNERS = {
{ n: '5', label: 'sites ✓' },
],
},
+
+ 'syncing-a-self-improving-ai-agent-across-machines': {
+ titlebar: '~/hermes — sync agent across machines',
+ lines: [
+ { t: 'prompt', text: '$' }, { t: 'cmd', text: 'git pull --ff-only origin main' },
+ { t: 'err', text: 'refusing: local divergence · never --force' },
+ { t: 'dim', text: 'distribution repo skills/ is a COPY, not the live dir' },
+ { t: 'prompt', text: '$' }, { t: 'cmd', text: 'diff -rq live ~/skills repo copy' },
+ { t: 'hl', text: 'Only in live/: hermes-profile-sync ← created after last push' },
+ { t: 'prompt', text: '$' }, { t: 'cmd', text: 'stage real skills only · commit · push · verify' },
+ { t: 'ok', text: '→ edbf7ac on both ends ✓ · 172 skills · 49MB junk stripped' },
+ ],
+ flow: [
+ { n: '1', label: 'pull --ff-only' },
+ { n: '2', label: 'diff 3-way', err: true },
+ { n: '3', label: 'merge / ask' },
+ { n: '4', label: 'push' },
+ { n: '5', label: 'verify ✓' },
+ ],
+ },
};
const DEFAULT_BANNER = {
diff --git a/scripts/og-gen/generate.mjs b/scripts/og-gen/generate.mjs
index e8ca240..cfb2584 100644
--- a/scripts/og-gen/generate.mjs
+++ b/scripts/og-gen/generate.mjs
@@ -88,6 +88,11 @@ const TERMINALS = {
$wget -qO- ipv4.icanhazip.com
bind: permission denied · CapEff=0 on :80
$internal:true · :8080 · SocksPort 0→ zero egress ✓
`,
+
+ 'syncing-a-self-improving-ai-agent-across-machines': `
+ $git pull --ff-only origin main
+ refusing: local divergence · never --force
+ $diff -rq live skills · merge on conflict→ synced ✓
`,
};
const DEFAULT_TERMINAL = `
diff --git a/src/content/posts/syncing-a-self-improving-ai-agent-across-machines.md b/src/content/posts/syncing-a-self-improving-ai-agent-across-machines.md
new file mode 100644
index 0000000..5ae56f6
--- /dev/null
+++ b/src/content/posts/syncing-a-self-improving-ai-agent-across-machines.md
@@ -0,0 +1,197 @@
+---
+title: "Syncing a Self-Improving AI Agent Across Machines"
+description: "How I sync my Hermes agent's skills, config, and cron jobs between two PCs with a git-backed profile distribution — and why I sync manually instead of on a cron."
+pubDate: 2026-09-11
+category: case-studies
+tags: [hermes, git, gitea, ai-agents, devops, sync]
+ogImage: /og/syncing-a-self-improving-ai-agent-across-machines.png
+banner: /banners/syncing-a-self-improving-ai-agent-across-machines.png
+---
+
+My agent learns. Every time I walk it through a non-trivial workflow — how to
+publish to Gitea, how to troubleshoot a flaky Docker container, how to write a
+Shopee listing — it saves that as a *skill*: a reusable procedure it loads the
+next time the task comes up. Over a few months that grew to 172 skills across
+twenty categories. They're my second brain.
+
+The problem: that brain lives on one machine. When I sit down at my other PC,
+the agent is *dumber* — it doesn't know what I taught it on the first one.
+
+This is the story of how I made the agent's knowledge follow me between
+machines, why the obvious approach would have silently destroyed work, and the
+one decision that turned out to matter most.
+
+## Why this matters
+
+If you run any self-improving agent — Hermes, Claude Code, Codex, Cursor — you
+have this exact problem. The agent accumulates knowledge locally, and none of it
+moves. You either re-teach it on every machine (pointless) or you let the two
+copies drift until they contradict each other (worse).
+
+Solving it properly means three things, and they're the same three things any
+engineer cares about when syncing *any* state:
+
+- **A shared source of truth** — one canonical copy both machines pull from.
+- **No silent data loss** — a sync that can't clobber work you did on the other
+ machine.
+- **A decision point** — when two machines changed the same thing, *you* pick,
+ not the tool.
+
+Get those three right and your agent is genuinely the same person on both
+machines. Get them wrong and you spend a weekend reconstructing a skill a bad
+`--force` wiped out.
+
+## The three official mechanisms
+
+Hermes ships three ways to move an agent, and they answer different questions:
+
+| Need | Mechanism | Command |
+|---|---|---|
+| Just skills | Skills Hub "tap" (a git repo of `SKILL.md` folders) | `hermes skills tap add` + `install` |
+| Whole agent, versioned, pullable | **Profile distribution** (git repo + `distribution.yaml`) | `hermes profile install` / `update` |
+| One-off move / backup | Export file (`.tar.gz`) | `hermes profile export` / `import` |
+
+For "same agent on two machines," the **profile distribution** is the durable
+answer. It packages the whole agent — SOUL, config, skills, cron — as a git
+repo, and the other machine pulls updates with one command.
+
+I already run Gitea (git.hoelee.com) for everything else, so a distribution
+repo there was the natural fit. No new tooling, no new accounts, and it plays
+by the same Gitea-first convention I use for every project.
+
+## The trap I almost walked into
+
+The distribution installer has a safety property that *sounds* like a
+limitation but is exactly what saves you: **it refuses to install into the
+`default` profile.** You must give it a named profile.
+
+That's not an annoyance. The second PC already had its own agent with
+project-specific skills. If I'd installed the distribution *over* it — or used
+`--force`, which overwrites an existing profile — I'd have deleted that
+machine's local work. The `default`-refusal is the guardrail that forces you to
+install *alongside* the existing agent, then merge.
+
+Which is the real lesson of this whole exercise: **the merge is the product.**
+The sync isn't "copy files." It's reconciling two agents that both think
+they're the source of truth.
+
+## The layout fact that would have clobbered me
+
+Here's the subtle part, and it's the kind of thing that looks fine until it
+isn't.
+
+The distribution repo holds a **copy** of the skills tree — it is *not* the
+live directory. The live skills live in `~/AppData/Local/hermes/skills/` (where
+the agent actually writes); the repo copy is a snapshot I push. They are two
+separate trees that drift.
+
+So a sync actually has **three parties**, not two:
+
+```
+live skills dir ←→ repo copy (D:\dev\hoelee-agent-dist\skills) ←→ Gitea remote
+```
+
+A skill I create after the last push exists *only* in the live dir. If I'd
+assumed the repo copy was authoritative and "synced" from it, I'd have
+silently deleted that skill. The first thing I do before any sync is diff the
+two trees:
+
+```bash
+diff -rq ~/AppData/Local/hermes/skills D:/dev/hoelee-agent-dist/skills
+```
+
+Divergence is *normal*, not an error. The whole discipline is knowing which of
+the three parties is ahead and reconciling deliberately.
+
+## Why I sync manually, not on a cron
+
+This was the decision that mattered most, and it came down to the conflict
+policy. I want two-way sync — both machines edit skills — and when both edit
+the *same* skill, I want to **keep both and pick myself**, not have a tool
+silently drop one.
+
+That policy is inherently interactive. A cron job that auto-pulls either
+overwrites work or fires when I'm not looking. So I made the trigger manual: I
+say *"sync skills"*, the agent runs it, and it **stops and asks me on any
+conflict**. It's not laziness — it's that the merge decision is mine to make,
+and automation can't make it for me.
+
+The flow I documented (and now run) is:
+
+```bash
+# 1. Pull remote into the repo copy, fast-forward only — never force
+cd D:\dev\hoelee-agent-dist && git pull --ff-only origin main
+
+# 2. Reconcile live dir ↔ repo copy
+diff -rq ~/AppData/Local/hermes/skills D:/dev/hoelee-agent-dist/skills
+
+# 3. Stage only real skill content (never the curator junk)
+# 4. Commit + push, then verify the remote actually has it
+git ls-remote origin main # must match local HEAD
+```
+
+If the pull refuses, or a diff shows the same skill changed on both sides, I
+stop and decide. Never `--force`, never a reset.
+
+## The junk that was silently bloating the repo
+
+One concrete number from the first packaging: my skills directory was **57 MB**,
+but only **~8 MB** was actual skill content. The rest was runtime state the
+agent's curator writes constantly — a 40 MB hub index cache, curator backups,
+usage tracking, a ledger.
+
+None of that belongs in a shared repo. It changes daily, it's machine-local,
+and it would have made every sync a noisy mess. A `.gitignore` strips it:
+
+```gitignore
+# skill runtime junk — never ship
+skills/.hub/
+skills/.curator_backups/
+skills/.curator_ledger.jsonl
+skills/.usage.json
+skills/.bundled_manifest
+```
+
+The moment you package a self-improving agent, you discover it's not just
+files — it's files *plus* a layer of bookkeeping you have to keep out. Same
+lesson as a `.gitignore` for `node_modules`, but for your own memory.
+
+## What I'd do differently
+
+- **Think about the trigger before the mechanism.** I spent effort deciding
+ distribution-vs-export before I'd answered "do I want this manual or
+ automated?" — and the answer to that question *changed* the mechanism. If I'd
+ known I wanted manual, conflict-aware sync from the start, I'd have designed
+ the script that way on day one.
+- **Treat the copy-vs-live distinction as a named invariant.** I discovered it
+ by diffing; it should have been a line in my notes from the first `cp -r`.
+- **Decide per-machine `user_id` up front** for memory. Skills sync via git,
+ but episodic memory (mem0) is a separate backend — two machines either share
+ one memory bucket or keep separate ones, and that's a one-line config choice
+ I should have made explicit earlier.
+
+## The result
+
+I now have one git repo — `git.hoelee.com/hoelee/hoelee-agent-dist` — holding
+172 skills, my config, SOUL, and cron jobs. The other machine installs it into
+a named profile, merges its own skills in, and we stay in sync with a manual
+trigger that asks me on every conflict. The first real sync pushed a
+skill that existed only on the live machine, verified it landed (`edbf7ac` on
+both ends), and left every existing skill untouched.
+
+The honest takeaway for anyone syncing an agent: the *files* are easy — git
+does the heavy lifting. The hard parts are the guardrails: the `--force` you
+must never run, the copy you must never mistake for the source of truth, and
+the conflict you must never let a tool resolve silently.
+
+---
+
+## Want this for your own agent setup?
+
+I build self-hosted AI infrastructure — agents, bots, and automation — for
+businesses and individuals. If you're running an agent across machines (or want
+to start), or need a hand with self-hosted tooling, I'd love to talk:
+
+- 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969)
+- 📧 **Email:** [me@hoelee.com](mailto:me@hoelee.com?subject=Agent%20sync%20setup)
+- 🌐 **Website:** [hoelee.com](https://hoelee.com)
diff --git a/src/content/posts/zh/syncing-a-self-improving-ai-agent-across-machines.md b/src/content/posts/zh/syncing-a-self-improving-ai-agent-across-machines.md
new file mode 100644
index 0000000..cb1566b
--- /dev/null
+++ b/src/content/posts/zh/syncing-a-self-improving-ai-agent-across-machines.md
@@ -0,0 +1,130 @@
+---
+title: "在多台机器之间同步一个会自我学习的 AI Agent"
+description: "如何用基于 git 的 profile distribution 在两台电脑之间同步 Hermes agent 的技能、配置和定时任务——以及为什么我选择手动同步而不是用 cron。"
+pubDate: 2026-09-11
+category: case-studies
+tags: [hermes, git, gitea, ai-agents, devops, sync]
+ogImage: /og/syncing-a-self-improving-ai-agent-across-machines.png
+banner: /banners/syncing-a-self-improving-ai-agent-across-machines.png
+---
+
+我的 agent 会学习。每当我带它走一遍不平凡的工作流程——如何发布到 Gitea、如何排查一个时好时坏的 Docker 容器、如何写一条 Shopee 商品列表——它就会把过程存成一项 *skill*:一个下次遇到同类任务时自动加载的可复用流程。几个月下来,它累积了 172 项技能,横跨二十个分类。它们就是我的第二大脑。
+
+问题在于:这个大脑只存在于一台机器上。当我换到另一台电脑工作时,agent 就变"笨"了——它不知道我在第一台机器上教过它什么。
+
+这篇文章讲的是我如何让 agent 的知识跟着我在机器之间流动,为什么那个显而易见的做法会悄悄毁掉工作成果,以及最后发现最关键的那个决定。
+
+## 为什么这很重要
+
+如果你在运行任何会自我学习的 agent——Hermes、Claude Code、Codex、Cursor——你都会遇到一模一样的问题。agent 在本地累积知识,但这些知识不会跟着你走。你要么在每台机器上重新教它一遍(毫无意义),要么让两份副本逐渐漂移、直到互相矛盾(更糟)。
+
+正确地解决它需要三件事,也正是任何工程师在同步*任意*状态时都关心的三件事:
+
+- **一个共享的真相来源**——一份两台机器都能拉取的权威副本。
+- **不静默丢数据**——同步绝不能覆盖你在另一台机器上做过的工作。
+- **一个决策点**——当两台机器改动了同一处内容时,由*你*来做选择,而不是让工具替你决定。
+
+把这三件事做对,你的 agent 在两台机器上就是同一个人。做错了,你就得花一个周末去重建一个被错误的 `--force` 抹掉的技能。
+
+## 三种官方机制
+
+Hermes 提供了三种迁移 agent 的方式,它们回答的是不同的问题:
+
+| 需求 | 机制 | 命令 |
+|---|---|---|
+| 只要技能 | Skills Hub "tap"(一个 `SKILL.md` 文件夹的 git 仓库) | `hermes skills tap add` + `install` |
+| 整个 agent、带版本、可拉取更新 | **Profile distribution**(git 仓库 + `distribution.yaml`) | `hermes profile install` / `update` |
+| 一次性迁移 / 备份 | 导出文件(`.tar.gz`) | `hermes profile export` / `import` |
+
+对于"同一 agent 在两台机器上",**profile distribution** 才是持久的答案。它把整个 agent——SOUL、配置、技能、定时任务——打包成一个 git 仓库,另一台机器用一条命令就能拉取更新。
+
+我所有东西本来就用 Gitea(git.hoelee.com),所以在那里放一个 distribution 仓库是顺理成章的。没有新工具、没有新账号,也符合我每个项目都遵循的"Gitea 优先"惯例。
+
+## 我差点踩进去的坑
+
+distribution 安装器有一个安全特性,*听起来*像限制,却恰恰是救命的东西:**它拒绝安装到 `default` profile 里。** 你必须给它一个命名 profile。
+
+这不是麻烦。第二台电脑本来就有自己的 agent 和项目相关的技能。如果我把它*覆盖*安装上去——或者用了会覆盖现有 profile 的 `--force`——我就会删掉那台机器本地的成果。`default` 拒绝机制就是那道护栏,逼着你在现有 agent *旁边*安装,然后再合并。
+
+而这正是整个练习真正的教训:**合并才是产品本身。** 同步不是"复制文件",而是调和两个都以为自己是真相来源的 agent。
+
+## 那个差点让我翻车的目录事实
+
+这里是微妙的部分,也是那种"看起来没事、直到出事"的坑。
+
+distribution 仓库里放的是技能树的**副本**——它不是*活跃目录*。活跃技能在 `~/AppData/Local/hermes/skills/`(agent 真正写入的地方);仓库副本只是我推送的一份快照。它们是两棵会漂移的、互相独立的树。
+
+所以一次同步实际上涉及**三方**,而不是两方:
+
+```
+活跃技能目录 ←→ 仓库副本(D:\dev\hoelee-agent-dist\skills) ←→ Gitea 远端
+```
+
+我在上次推送之后新建的一个技能,*只*存在于活跃目录里。如果我假设仓库副本是权威、并"从它开始同步",就会静默删掉那个技能。所以每次同步前我做的第一件事,就是比对两棵树:
+
+```bash
+diff -rq ~/AppData/Local/hermes/skills D:/dev/hoelee-agent-dist/skills
+```
+
+漂移是*正常的*,不是错误。整套纪律在于搞清楚三方里谁领先,然后有意识地调和。
+
+## 为什么我手动同步,而不是用 cron
+
+这是最关键的决定,而它归结为冲突策略。我想要双向同步——两台机器都会编辑技能——而当两边编辑了*同一个*技能时,我想**两边都保留、由我自己来挑**,而不是让工具静默丢掉其中一个。
+
+这种策略本质上是交互式的。一个自动拉取的 cron 要么覆盖成果,要么在我没盯着的时候触发。所以我让触发变成手动:我说一句*"同步技能"*,agent 就去执行,并在**任何冲突时停下来问我**。这不是偷懒——而是合并的决定权在我手里,自动化做不了这个决定。
+
+我记录下来的(也是现在真正在跑的)流程是:
+
+```bash
+# 1. 把远端拉进仓库副本,只快进——绝不 force
+cd D:\dev\hoelee-agent-dist && git pull --ff-only origin main
+
+# 2. 调和活跃目录 ↔ 仓库副本
+diff -rq ~/AppData/Local/hermes/skills D:/dev/hoelee-agent-dist/skills
+
+# 3. 只暂存真正的技能内容(绝不包含 curator 垃圾)
+# 4. 提交 + 推送,然后验证远端确实有了
+git ls-remote origin main # 必须等于本地 HEAD
+```
+
+如果拉取被拒绝,或者 diff 显示同一个技能在两边都被改过,我就停下来做决定。绝不 `--force`,绝不 reset。
+
+## 那坨悄悄撑大仓库的垃圾
+
+第一次打包时的一个具体数字:我的技能目录有 **57 MB**,但真正的技能内容只有 **~8 MB**。剩下的是 agent 的 curator 不断写入的运行时状态——一个 40 MB 的 hub 索引缓存、curator 备份、使用统计、一份账本。
+
+这些都不该进共享仓库。它们每天都在变、是机器本地的、而且会让每次同步都变成一团糟。一个 `.gitignore` 就能把它们剥掉:
+
+```gitignore
+# skill 运行时垃圾——绝不入库
+skills/.hub/
+skills/.curator_backups/
+skills/.curator_ledger.jsonl
+skills/.usage.json
+skills/.bundled_manifest
+```
+
+当你打包一个会自我学习的 agent 时,会发现它不只是文件——是文件*加上*一层必须挡在外面的簿记。跟给 `node_modules` 写 `.gitignore` 是同一个道理,只不过对象是你自己的记忆。
+
+## 我会做得不一样的地方
+
+- **先想清楚触发方式,再选机制。** 我花了不少功夫纠结 distribution 还是 export,却还没回答"我要手动还是自动"——而这个答案反过来会*改变*机制。如果我一开始就知道要的是手动的、冲突感知的同步,第一天就会照这个方式去设计脚本。
+- **把"副本 vs 活跃目录"的区分当作一条有名字的不变量。** 我是靠 diff 才发现的;它本该从第一次 `cp -r` 起就写进我的笔记里。
+- **提前决定每台机器的 `user_id`。** 技能走 git 同步,但情节记忆(mem0)是另一个后端——两台机器要么共享一个记忆桶、要么各留各的,这是一个应该早点明确的单行配置选择。
+
+## 结果
+
+现在我有一个 git 仓库——`git.hoelee.com/hoelee/hoelee-agent-dist`——装着 172 项技能、我的配置、SOUL 和定时任务。另一台机器把它装进一个命名 profile,再把自己本地的技能合并进去,我们靠一个"每次冲突都问我"的手动触发器保持同步。第一次真正的同步推送了一个只存在于活跃机器上的技能,验证它落地了(两边都是 `edbf7ac`),并且没有动任何现有的技能。
+
+给任何想同步 agent 的人的一句实在话:*文件*是简单的——git 干了大部分重活。难的是那些护栏:那个你绝不能跑的 `--force`,那个你绝不能误当成真相来源的副本,还有那个你绝不能让工具替你静默解决的冲突。
+
+---
+
+## 想为自己的 agent 搭建这套东西吗?
+
+我为企业和个人搭建自托管的 AI 基础设施——agent、bot、自动化。如果你正在跨机器运行 agent(或想开始),或者需要自托管工具方面的帮助,我很乐意聊聊:
+
+- 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969)
+- 📧 **Email:** [me@hoelee.com](mailto:me@hoelee.com?subject=Agent%20sync%20setup)
+- 🌐 **Website:** [hoelee.com](https://hoelee.com)