VPS Notes
VPS

Hermes Agent を Claude Code だけで自作する: 記憶が育つ常駐エージェント「iris」を VPS に置く

VPS に常駐する Claude Code の Remote Control を自分専用エージェントにしました。memory・skills・notes の 3 層で学んだことを記憶し、cron で週次レビュー、結果は Telegram へ。常駐プロセス追加なし、API 課金なし。Hermes との使い分けも。

claude-codeai-agenthermes-agentcrontelegramxserver-vps

本記事にはプロモーション (アフィリエイト広告) が含まれます。

前回 Hermes Agent を VPS で動かした結論は「スマホから指示を出すだけなら Claude Code の Remote Control で足りる」でした。では Hermes の本体である「学んだことを記憶し、長く動かすほど能力が高まる」は、Claude Code で作れるのか。作れました。

VPS に常駐する Claude Code の Remote Control に CLAUDE.md で役割と記憶のルールを与え、memory (事実)・skills (手順)・notes (履歴) の 3 層で学習させます。 定時の仕事は Linux の cron から claude -p が同じ記憶を使って行い、結果は Telegram に届きます。追加の常駐プロセスはゼロ、Claude Pro の契約枠で動くので API 課金もゼロです。名前は iris (イリス)。ギリシャ神話で Hermes の同僚の伝令神です。

つまずいたのは主に 2 点。常駐セッションは tmux send-keys でもピア間メッセージでも外から動かせず、cron は「常駐に頼む」のではなく「別プロセスが同じ記憶で動く」形にしました。もう 1 つ、権限の設定ファイルが効かず、起動フラグで渡す必要がありました。

作るもの

Hermes Agent が持っていた機能を、Claude Code 1 つに寄せます。

機能 Hermes Agent iris
指示の入口 Telegram gateway (常駐) Remote Control (スマホの Claude アプリ)
記憶 MEMORY.md + skills + session_search memory/MEMORY.md + skills/ + notes/ (git 管理)
学習のループ システムプロンプトに組み込み CLAUDE.md に自分で書く
定時実行 独自スケジューラ (毎回新しいセッション) Linux の cron → claude -p (同じ記憶で動く)
通知 Telegram home channel curl で Telegram Bot API (スクリプト 10 行)
費用 cron 1 回 8 円 (API) 0 円 (Pro の枠内)

この記事で使う環境

XServer VPS クラウド
4GB (4 コア) で月 2,480 円〜。この記事の検証環境。常駐するのは Claude Code 1 つで、iris のプロセスは 280MB ほどです。2GB プランでも動きます。
公式サイトを見る

1. フォルダを作る

エージェントの本体は 1 つのフォルダです。git で管理して、記憶の変化を追えるようにします。

~/iris/
├── CLAUDE.md          役割、記憶のルール、学習のループ
├── memory/MEMORY.md   事実 (毎回自動で読む)
├── skills/INDEX.md    手順の索引 (毎回自動で読む)
├── skills/<名前>.md   手順の本体 (該当する作業のときに読む)
├── notes/YYYY-MM-DD.md  履歴 (会話の要点を毎回追記)
├── ideas/             週次レビューの出力 (記事候補)
├── bin/notify-telegram   Telegram に送る
├── bin/weekly-review     cron から呼ぶ週次レビュー
└── .env               Telegram のトークン (git 管理外)

2026-09-26 に、iris の手順書 (skills/) を Claude Code 公式のスキル (.claude/skills/<名前>/SKILL.md) に移しました。索引の INDEX.md はなくなっています。移した理由と前後の比較は「Claude Code のスキルの作り方」に書きました。

mkdir -p ~/iris/{memory,skills,notes,ideas,bin} && cd ~/iris
git init
printf '.env\n*.log\n' > .gitignore

2. CLAUDE.md に「記憶のルール」を書く

ここが Hermes との最大の違いです。Hermes は記憶と学習の仕組みがシステムプロンプトに組み込まれています。Claude Code では、それを CLAUDE.md に自分で書きます。逆に言えば、自分の運用に合わせて自由に変えられます。

私の CLAUDE.md の骨子です (全文は長いので要点だけ)。

# iris (イリス)

crz33 の常駐エージェント。VPS で `claude remote-control --name iris` として動き、
スマホの Claude アプリから話しかけられる。

目指す姿: **学んだことを記憶し、長く動かすほど能力が高まる。**
同じことを 2 度聞かれたら 2 度目は速く、同じ作業を 2 度やったら 2 度目は失敗しない。

## 記憶の 3 層

| 層 | 場所 | 何を書くか | いつ読むか |
|---|---|---|---|
| 事実 | `memory/MEMORY.md` | crz33 のこと、環境、方針、好み | 毎回 (下で自動読込) |
| 手順 | `skills/<名前>.md` | 一度やった作業のやり方、つまずき | 該当する作業のとき |
| 履歴 | `notes/YYYY-MM-DD.md` | 何を聞かれ、何を答えたか | 過去を探すとき |

@memory/MEMORY.md
@skills/INDEX.md

## 学習のループ (毎回やる)

会話や作業が一区切りついたら、頼まれなくても次の 3 つを自問して書く。
書いたら git commit まで行う。

1. 事実が出てきたか → `memory/MEMORY.md` に 1 行追記
2. 手順として残せるか → `skills/<名前>.md` を書くか更新。失敗したときこそ書く
3. 必ず → `notes/YYYY-MM-DD.md` に要点を追記

作業を始める前は、逆にこの順で読む。
「前にやったことがある気がする」ときは必ず探す。

ポイントは 2 つです。

@memory/MEMORY.md の行。Claude Code の CLAUDE.md は @パス で他のファイルを取り込めます。これで memory と skills の索引が毎回のセッション開始時に読まれ、Hermes の「MEMORY.md がシステムプロンプトに入る」と同じ状態になります。skills の本体は索引だけ読んで、該当するときに開きます。Hermes の skills と同じ設計です。

「頼まれなくても書く」。これを書かないと、Claude は答えるだけで記録しません。「会話が一区切りついたら」「commit まで」と具体的に書くことで、毎回の会話の最後に notes が増えていきます。

memory の初期値は自分で数行書きます。

# memory

## crz33
- vpsnotes.com (VPS × AI エージェント × セルフホストの実践記録) を書いている (2026-09-21)

## 環境
- サーバー: XServer VPS クラウド 4GB、Ubuntu 24.04、ホスト名 vpsnotes-001。SSH は Tailscale のみ。sudo は使えない (2026-09-21)
- Telegram 通知は `bin/notify-telegram`。トークンは `.env` (2026-09-21)

3. 常駐させる

Remote Control を tmux の中で立てます。記事 2 と違うのは、フラグが 3 つ増えたことです。起動のコマンドは、後で再起動するときにも使うので、スクリプトにしておきます。

cat > ~/iris/bin/start <<'EOS'
#!/bin/bash
# iris の常駐 (Remote Control) を tmux で立てる。@reboot と bin/restart から呼ぶ
set -eu
if tmux has-session -t iris 2>/dev/null; then
  echo "already running"; exit 0
fi
tmux new -d -s iris -c "$HOME/iris" \
  "$HOME/.local/bin/claude remote-control --name iris --spawn=same-dir --permission-mode bypassPermissions"
echo "started"
EOS
chmod +x ~/iris/bin/start && ~/iris/bin/start

再起動後の復帰は crontab の @reboot です。

@reboot sleep 20 && /home/deploy/iris/bin/start

再起動のスクリプトも置きます。後で書くように、コネクタを繋いだときや、CLAUDE.md を大きく変えたときに使います。

cat > ~/iris/bin/restart <<'EOS'
#!/bin/bash
# iris を再起動する。iris 自身が実行しても止まらないよう、切り離した子プロセスで行う
setsid -f bash -c "
  sleep 1
  tmux kill-session -t iris 2>/dev/null
  sleep 3
  $HOME/iris/bin/start
" >/dev/null 2>&1
echo "restarting (約 30 秒で戻ります)"
EOS
chmod +x ~/iris/bin/restart

setsid -f で切り離しているのは、iris 自身がスマホからの「再起動して」を受けてこのスクリプトを実行したとき、tmux を消した瞬間に自分も消えて、起動し直す前に止まってしまうのを防ぐためです。CLAUDE.md に「再起動して、と言われたら bin/restart を実行してよい」と書いておくと、スマホから頼めます。

新しいフォルダで初めて claude remote-control を動かすと、Error: Workspace not trusted で止まります。一度そのフォルダで claude を対話で起動して、信頼の確認に答えておきます。cron からの起動ではこの確認に答えられないので、先に済ませておく必要があります。

--permission-mode bypassPermissions は「このフォルダで動く Claude が何でも実行できる」設定です。私は sudo なし・SSH は Tailscale 内のみ、という VPS なので許容しました。被害範囲はこのユーザーのホームまでです。

4. 使ってみる: 聞いたことが記録される

スマホの Claude アプリで iris を開いて、普通に質問します。私は「Even G2 (スマートグラス) を店頭販売している店は? 愛知県高浜市周辺で」と聞きました。

iris は Web を調べて答えた後、頼んでいないのに notes/2026-09-21.md に要点を追記して commit しました。アプリの画面には「Appended note and committed」とだけ出ます。

claude.ai/code で iris に Even G2 の取扱店を聞き、答えの後に「Appended note and committed」と出ているところ

VPS 側で見ると、こう残っています。

cd ~/iris && git log --oneline | head -3 && tail -6 notes/2026-09-21.md
2bfd7bc notes: 2026-09-21 Even G2 取扱店
d350e6a claude -p に --dangerously-skip-permissions。効かない settings.json は削除
23d995a ideas: 2026-09-21 週次記事候補

## 13:40 Even G2 取扱店
crz33 から「Even G2 (スマートグラス) を店頭販売している店」を質問。
- 結論: 愛知県内の実機展示は 2 店。日本代理店は株式会社Acalie
- 度付きレンズが必要な場合は公式サイトの認定眼鏡店マップを案内

この時点の CLAUDE.md にはまだ memory の層がなく、「Even G2 に興味がある」の 1 行は私が memory/MEMORY.md に移しました。今のルールでは、iris が会話の最後に自分で足します。次にこの話題を出したとき、iris は前提を知っている状態から始まります。

memory が効いているかは、claude -p で確かめられます。「ファイルを読まずに答えて」と釘を刺すのがコツです。

cd ~/iris && claude -p "ファイルを読まずに、今すでに知っている範囲で答えて: crz33 が興味を持っているガジェットと、使える skill の名前は?"
crz33 が興味を持っているガジェット: Even G2(スマートグラス)。
使える skill: server-health(サーバーの空き容量・メモリ・失敗ユニットを確認する)。

@ で取り込んだ memory と skills の索引が、セッション開始時に入っています。

5. Telegram に通知する

Bot API の sendMessage を叩くだけなので、スクリプトを 1 つ置きます。トークンと chat_id は .env に分けます。

cat > ~/iris/.env <<'EOS'
TELEGRAM_BOT_TOKEN=1234567890:AA...
TELEGRAM_CHAT_ID=123456789
EOS
chmod 600 ~/iris/.env

chat_id は自分の Telegram のユーザー ID です。Bot に何か送ってから https://api.telegram.org/bot<トークン>/getUpdates を開くと "chat":{"id":...} に出ます。

cat > ~/iris/bin/notify-telegram <<'EOS'
#!/bin/bash
# 使い方: notify-telegram "メッセージ"  または  echo "メッセージ" | notify-telegram
set -eu
source "$HOME/iris/.env"
MSG="${1:-$(cat)}"
curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
  --data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
  --data-urlencode "text=${MSG}" >/dev/null && echo "sent"
EOS
chmod +x ~/iris/bin/notify-telegram
~/iris/bin/notify-telegram "iris です。VPS から通知のテスト"

CLAUDE.md に「結果を知らせるときは bin/notify-telegram を使う」と書いておけば、iris が Bash で呼びます。

6. 週次レビューを cron に置く

Hermes の cron に相当する部分です。ここで最初の設計が崩れました。

常駐セッションに「外から」頼む方法は全部だめだった

最初は、cron から常駐セッションにメッセージを送って仕事をさせるつもりでした。試した順に書きます。

  1. tmux send-keys: 動きません。claude remote-control は対話の REPL ではなく、claude.ai からの接続を受けるサーバーです。画面には QR コードしかなく、入力欄がありません
  2. ピア間メッセージ (SendMessage): Claude Code には、同じマシンの別セッションにメッセージを送る機能があります。cron から claude -p "常駐セッションに SendMessage で送って" とすれば届きます。実際、送信は成功します。しかし 待機中のセッションは受け取ったメッセージを処理しません。スマホでアプリを開いた瞬間に動き出したので、「誰かが見ているときだけ動く」仕組みです。無人の cron には使えません

結論は「常駐に頼まない」です。cron の仕事は、使い捨ての claude -p が同じ ~/iris で動いてこなします。CLAUDE.md も memory も skills も同じものを読むので、常駐の iris と同じ知識で動きます。違うのは会話の文脈を持たないことだけで、定時の仕事にそれは要りません。

weekly-review

cat > ~/iris/bin/weekly-review <<'EOS'
#!/bin/bash
# 週 1 回 (土曜 5:00): notes を読み返して記憶を育て、記事候補を出す (cron から呼ぶ)
set -eu
export PATH=$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin
cd "$HOME/iris"
timeout 900 claude -p "【週次レビュー】これは無人の cron 実行で、私に質問はできない。次の 2 つを順にやって。
1. notes/ の直近 2 週間分を読み返す。memory/MEMORY.md に足すべき事実があれば追記し、skills/ に残すべき手順 (2 回以上やった作業、つまずいた作業) があれば書いて INDEX.md に足す。既存と矛盾があれば直す。変更は commit する
2. 先に memory/vpsnotes-plan.md (ブログの公開済み・下書き・計画の一覧) を読み、それと重ならない記事候補を 3 つ挙げる。ideas/$(date +%Y-%m-%d).md に保存して commit する
最後に、1 で何を足したか (なければ「なし」) と、2 の候補 3 つを、bin/notify-telegram で 1 通にまとめて送って" \
  --model claude-opus-5-5 --dangerously-skip-permissions >> "$HOME/iris/weekly-review.log" 2>&1
EOS
chmod +x ~/iris/bin/weekly-review

Hermes の curator (記憶の整理役) と、私の用途である「記事候補」を 1 本にまとめています。プロンプトの「私に質問はできない」は重要で、これがないと claude -p が「材料が足りませんが、どうしますか?」と聞き返して、何もせず終わります (実際にそうなりました)。

--dangerously-skip-permissions も要ります。claude -p は非対話なので、承認が要る操作は自動で拒否されます。

手で 1 回動かして、Telegram に届くのを見てから crontab に入れます。

~/iris/bin/weekly-review
(crontab -l; echo "0 5 * * 6 /home/deploy/iris/bin/weekly-review") | crontab -

初回は notes が 1 日分しかなく、候補も薄いものでした。それでも ideas/2026-09-21.md に 3 件書いて commit し、Telegram に届きました。流れは通っています。

初めての自動実行は、2026-09-26 (土) 5:00 でした。1 分かからずに終わり、Telegram にこう届きました (一部を省略)。

【週次レビュー 2026-09-26】
■ 1. 記憶の更新
memory:
・事実: Gmail はアーカイブ運用、タブはメイン/プロモーションのみ
・事実: FX ボットは数分〜数時間のトレンドフォロー、USD/JPY 候補
・方針: 数千件の Gmail 一括操作は Gmail 画面で
・方針: ログ件数は 1 イベント 1 行で数える
skills:
・新規 gmail-cleanup.md (広告整理・一括削除・受信トレイにない理由の調べ方)
・server-health.md の矛盾を修正

■ 2. 記事候補 (ideas/2026-09-26.md)
1) Claude に Gmail を整理させる: コネクタでできること・できないこと
2) VPS を公開して 2 日で SSH 総当たり 1 万件: auth.log の正しい数え方
3) Claude Code で claude.ai のコネクタを使う: ツールが出ないときは再起動

3 日分の notes から、memory に 4 行、skills に 1 本が昇格しました。記事候補からは、notes にあった「音楽サブスクの比較」と「Even G2 の取扱店」を、サイトのテーマから外れるとして自分で外していました。記憶がどう育ったかの中身は、CLAUDE.md の書き方の記事 に詳しく書きました。

7. 動かしながら変えたこと

作ってから 3 日で、iris の周りを 3 つ変えました。どれも「常駐のエージェントならでは」の手間です。

Gmail を繋ぐ: コネクタは再起動しないと出てこない

claude.ai の設定で Gmail のコネクタを繋ぐと、スマホの iris からメールを検索したり整理したりできるようになります。ただ、繋いだ直後の iris からは Gmail のツールが見えませんでした。コネクタのツールの一覧は、セッションの起動時に読み込まれる からです。bin/restart で iris を立て直すと出てきました。

面白かったのは、iris がこの手順を自分で skill にしたことです (skills/connector-setup.md)。次に Google カレンダーや Drive を繋ぐときは、この skill に手順とつまずきが書いてあります。

モデルを Opus 5.5 に上げる: 古いセッションでは選べない

途中で Claude Opus 5.5 が出たので、iris のモデルを上げました。ここで 2 つ詰まりました。

1 つ目は、VPS の Claude Code が古かったことです。claude -p --model claude-opus-5-5 を試すと、「Claude Code 2.1.271 はこのモデルに対応していない。2.1.280 以降が必要」というエラーが返りました。claude update で上げると使えるようになりました。

2 つ目は、Remote Control のセッションのモデルです。これは VPS の設定ではなく、スマホのアプリの入力欄にあるモデル選択で決まります。ところが、アプリの選択肢で Opus 5.5 だけが灰色で、「このモデルを使用するには、このセッションを実行しているマシンで Claude Code を更新してください」と出ました。VPS は更新済みです。古い版で作ったセッションが、その情報を持ち続けていた のです。同じフォルダで 4 時間以内に再起動すると、前のセッションに戻ってしまうので、再起動でも直りません。アプリの「+ 新規」で新しいセッションを作ると、Opus 5.5 を選べました。

cron から動く claude -p の方は、~/iris/.claude/settings.json に {"model": "claude-opus-5-5"} と書けば効きました (権限の設定は効かなかったのに、モデルの設定は効きます)。

記事候補が重ならないように: ブログの記事一覧を送る

週次レビューに記事候補を出させるなら、iris はブログに何がもう書いてあるかを知らないといけません。知らないと、公開済みの記事と同じ候補を出します。そこで、記事を公開するたびに、手元の Mac から記事の一覧 (公開済み・下書き・計画・見送った候補) を VPS の ~/iris/memory/vpsnotes-plan.md に送るようにしました。週次レビューのプロンプトで「先にこれを読み、重ならないものだけ」と指示しています。

全体の流れ

スマホの Claude アプリ ──▶ iris (claude remote-control、常駐)
                            ├─ 答える
                            └─ memory / skills / notes に書いて commit
                                        │ 同じフォルダ、同じ記憶
Linux cron (土 5:00) ──▶ claude -p (使い捨て) ─┤
                            ├─ notes を読んで memory / skills に昇格
                            ├─ ideas/ に記事候補
                            └─ bin/notify-telegram ──▶ Telegram

Hermes と、どちらを選ぶか

Claude Code だけで足りる人: 使うモデルが Claude で、指示を出すのが自分 1 人で、Claude Pro を契約している。この記事の構成が最も軽く (iris のプロセスは 280MB)、費用ゼロで、記憶のルールを自分の言葉で書けます。

Hermes を選ぶ人: モデルを Claude 以外にしたい。家族や同僚と 1 つの Bot を共有したい。記憶や学習のループを自分で設計せず、製品として整ったものを使いたい。Hermes は cron の永続化や [SILENT] の抑止など、細かい作り込みがあります。

私は 1 人で Claude しか使わないので、Hermes は消して iris に寄せました。記憶のルールを CLAUDE.md で自分の言葉にできるのが、いちばんの理由です。

つまずいた点

iris の git の記録では、フォルダを作ってから週次レビューを手で初めて動かすまでが 20 分でした。記憶を 3 層に分けるルールは、翌朝に足しています。その前に、「常駐に外から頼む」方法を試して捨てる時間がかかりました。

次にやること

まとめ

XServer VPS クラウド
この記事の検証環境。常駐するのは Claude Code 1 つなので 2GB プランでも動きますが、Immich などと同居させるなら 4GB です。
公式サイトを見る