背景
前回のPersonal AI Agentのためのデータ基盤設計思想の記事では、AI Agentが個人データを横断参照できる中心を手元に持つという考え方を整理し、その中心を Personal Context Hub と名づけました。
yutashx.hatenablog.com
本記事はその続きで、Personal Context Hub を実際にどう実装しているかと、Hermes Agentからどう使っているかを書きます。
なお私が現在 Personal AI Agent として使っているのは Hermes Agent です。以前 OpenClaw も試しましたが、当時の自分の用途では tool use 周りの安定性や設定の見通しに課題を感じ、Hermes に移りました。ただ、本記事の主題は Hermes そのものではありません。前回の記事 で書いたとおり、Personal Context Hub の目的は、特定の Agent にデータ資産を縛らないことにあります。本記事で「Hermes」と書く箇所は、現時点での接続先の一例として読んでもらえればと思います。
この記事で説明するもの
ここで紹介するのは、個人データをすべて1つのDBに押し込む巨大なDWHではありません。目指しているのは、Personal AI Agentが生活データへ迷わず到達できる最小限の基盤です。
具体的に扱うのは次の点です。
ヘルスケア・位置・写真・家計・メモ・予定などを、どこに置いているか
push / batch / on-demand をどう使い分けて集めているか
Hermes Agent から REST API・Postgres直読み・一次ソースをどう読み分けているか
raw データと解釈済みデータをどう分離しているか
監視・バックアップ・secrets・外部アクセスを個人運用でどう回しているか
全体構成
現在の構成は、単一ホストを中心に、AI Agentが個人データへ少ない経路で到達できるようにする方針です。
すべてを1つに物理統合しているわけではありません。写真はImmich、メモはObsidian Vault、予定や音楽は外部APIのオンデマンド取得です。そのうえで、横断分析に使う主要データはできるだけ手元のPostgresに寄せています。
全体構成図:外部サービス層・DB ストレージ層・エージェント層の 3 層と、蓄積・参照・オンデマンド取得の経路
主要コンポーネントは次の4つです。
postgres-shared: ヘルスケア、位置、家計、環境など、横断分析に使う主要データ
postgres-immich: 写真ライブラリ専用
Obsidian Vault: メモの一次ソース
Hermes Agent: これらを読み分けて、提案や要約を返す層
写真だけpostgres-immichとして別インスタンスに分けているのには理由があります。Immichは類似画像検索のために専用のベクトル拡張(vectorchord。Postgres上でベクトル検索を行う拡張)を必要とし、スキーマやマイグレーションもImmich本体が管理します。これを共用Postgresに同居させると、Immichのバージョン更新やスキーマ変更が、ヘルスケアや家計といった他データの可用性に波及してしまいます。だから写真基盤だけは共用Postgresから切り離しています。
この分け方にしているのは、全部を1つに押し込むためではなく、役割ごとに自然な保存先を保ちながら、Hermesからは一貫して参照できるようにするためです。
集めているデータ
私が現状集めているデータは次の通りです。
カテゴリ
データ内容
収集経路
頻度
ストレージ
ヘルスケア
睡眠 / 心拍 / 歩数 / 体重 / 運動
Health Auto Export iOS app → データ収集API
push
postgres-shared (hae)
位置
滞在地 / 移動 / 高度
Dawarich iOS app → dawarich-server
リアルタイム
postgres-shared (dawarich)
写真
撮影位置 / ランドマーク / 人物
Immich iOS app → immich-server
アップロード時
postgres-immich
家計
取引 / 残高 / 資産
MoneyForward から自前スクリプトで取り込み
1日1回
postgres-shared (agent_lake)
音楽
再生履歴
Spotify API
オンデマンド
永続化せず
メモ
Obsidianのノート
Obsidian Sync ↔ vault直参照
リアルタイム
Obsidian Vault
環境
室温 / 湿度 (SwitchBot Hub 2)
SwitchBot API から取得する自前スクリプト
数分間隔
postgres-shared (agent_lake)
予定
Google Calendar
gog コマンドを使って連携
オンデマンド
永続化せず
保存先がデータごとに違うのと同じく、集め方もソースによってまちまちです。
どう集めているか — push・batch・on-demand
データの集め方は、ソースの性質に合わせて大きく3通りに分けています。
push(iOSアプリから送られてくる) 。スマートフォンは、生活に最も密着した個人センサーハブです。位置・ヘルスケア・歩数・睡眠・心拍・写真・カレンダー……生活文脈の宝庫が、常に持ち歩くこの一台に集まっています。ただ、セルフホストのAI Agentからは、HealthKitや写真・位置情報に直接は手が届きません。そこを橋渡しするのが、目的別のiOSアプリです。ヘルスケアはHealth Auto Exportが自前APIの取り込みエンドポイントへ、位置はDawarichアプリが位置情報サーバーへ、写真はImmichアプリがアップロードします。いずれも端末側の変化を起点に送られてくるので、こちらから取りに行かなくても入ってくるのが特徴です。
batch(こちらから定期的に取りに行く) 。pushされてこないものは、自前のスクリプトを定期実行して取りに行きます。室温・湿度はSwitchBotのAPIから数分間隔で取得し、家計はMoneyForwardから自前のスクリプトで1日1回取り込んでいます。どちらもsystemd --userのタイマーで起動し、定期ジョブとして基盤側に常駐させています。ただし家計の取り込みは壊れやすく、あくまで個人用途の暫定的な仕組みなので、そのまま再現することは勧めません。
on-demand(Agentが問い合わせ時に取りに行く) 。予定や音楽のように、その都度の最新が欲しく永続化の必要が薄いものは、Hermesが問い合わせを受けたときに直接取得します。Google Calendarはgog コマンド経由、SpotifyはAPIをクエリ時に叩きます。手元のDBには残しません。
実装で効いているのは、前回の記事 で書いた「まず入れる、整理は後」という方針です。取り込み側ではスキーマを作り込まず、ソースが返す形にできるだけ近いrawを入れて、整える作業は後からSQLで書きます。集めるところに摩擦があると更新がすぐ止まるので、入口はとにかく軽くしています。
取り込みと配信を兼ねる薄いAPI — datalake-api
ヘルスケア・家計・環境のように取得パターンが安定したドメインには、datalake-apiという自前の薄いRESTレイヤを1枚かませています(TypeScript / Honoで書いています)。このAPIは2つの役割を兼ねています。
1つは取り込み口 です。Health Auto Export互換の取り込みエンドポイントを持っていて、iOSアプリから来るヘルスケアデータをそのまま受けてPostgresに流します。MoneyForwardを取りに行く収集処理も同じコードベースに同梱し、CLIとして1日1回動かしています。収集と配信を別リポジトリに散らさず、ドメインごとに1か所へまとめています。
もう1つは配信口 です。hae(ヘルスケア)・moneyforward(家計)・switchbot(環境)のようにドメインごとにルートを分けてあり、Hermesはそのエンドポイントを叩くだけで必要なデータを読めます。認証はミドルウェアで一括して受けています。
この層を挟む狙いは、前回の記事 で書いた「データ資産とAgent実装の結合を弱める」ことの実装版です。Agent側はSQLのスキーマやDBの認証情報を毎回知らなくてよく、安定したエンドポイントだけを見ればよくなります。
これはAgentの挙動とコストにも効きます。取得の仕方が毎回決まっていないと、Agentは「どのテーブルをどう叩くか」をその都度考え込み、余計な探索や試行錯誤に思考トークンを使ってしまいます。定型の取得を決まったエンドポイントに固定しておけば、Agentは迷わず必要なデータに届き、思考トークンは本当に判断が要る部分に回せます。安定したインターフェースを1枚かませることは、結合を弱めるだけでなく、Agentを迷わせない・余計なトークンを使わせないための設計でもあります。
Agentを乗り換えても、作り直すのはこのAPIを呼ぶ薄い部分だけで済みます。
Hermes Agentからどう使うか
この基盤は、データを貯めるだけでなく、Hermes Agentが個人データを横断参照するための土台として使っています。
Hermesからのアクセス経路は大きく3つです。
定型のドメインデータは自前REST API経由で取得する
横断分析が必要なときはPostgresを直接読む
メモや写真のように一次ソースを保った方が自然なものは、Vaultや各サービスAPIをそのまま読む
Hermes からの 3 つのアクセス経路:定型は REST API、横断は Postgres 直読み、メモや写真は一次ソースを直読み
ヘルスケアや家計のように取得パターンが安定しているものは、自前REST API経由でHermesから取得しています。
AgentはSQLや認証の詳細を毎回知らなくてよく、必要なドメインデータだけを読めます。
複数ドメインをまたぐ問いは、HermesがPostgresを直接読みます。
たとえば「旅行中、どの日に移動が多く、どの日に支出が多かったかを見たい」のような問いでは、位置、支出、写真、日付を横断して取得します。
こうした日次の横断をやりやすくするため、その日のヘルスケア・移動・支出・写真・室温・天気を1日1行にまとめた集計テーブルも基盤側に持っています。生データはドメインごとに分かれていますが、日次の粒度ではそこを1枚に束ねておく、という二段構えです。
公開している参照実装には、この日次集計テーブルのschemaを含めています。ただし、各データソースからそのテーブルを埋めるbackfill / sync jobは、実運用側の都合が強く出るためデモリポジトリには含めていません。ここでは「横断しやすい粒度を基盤側に1枚作る」という考え方を示す位置づけです。
Postgres直読みといっても、Hermesに管理者権限を渡しているわけではありません。横断分析用にread-onlyのユーザーを分け、参照できるschemaやviewを絞っています。rawテーブルへの書き込みはAgentからは行わせず、解釈や提案は別レイヤに保存する方針です。
メモはObsidian Vault、写真の類似検索はImmichのAPIのように、一次ソースをそのまま読んだ方が自然なものもあります。
全部をDB経由に揃えるのではなく、Hermesから見て扱いやすい経路を残しています。
rawは「真実」ではなく「証拠」として扱う
横断して使おうとすると、すぐに気づくことがあります。手元に集めたrawデータは、そのままでは「真実」ではない、ということです。
たとえば位置データは、滞在地の判定が途中から止まっていたり、移動速度が物理的にありえない値になっていたり、訪問地点が場所に紐づかないまま残っていたりします。センサーや外部サービスから入ってくるデータは、欠測・重複・明らかな異常値を含むのが前提です。これを「正しい記録」として読むと、横断した瞬間に破綻します。
なのでrawは、確定した事実ではなく「こういう観測があった」という証拠として持つようにしています。そして、rawデータと、それを解釈・加工したデータは、完全に別のレイヤとして分ける ことをポリシーにしています。rawは追記するだけで、上書きも削除もしません。睡眠・運動・滞在・移動のように出来事の単位へ正規化したり、日次に集計したりした「解釈済み」のデータは、rawとは別のテーブルに持ち、横断分析ではそちらを参照します。Agentが束ねた仮説や意味づけも同じで、rawに書き戻すことはありません。
この分離には実利があります。解釈のルールを変えても、別のAgentに乗り換えても、証拠であるrawさえ残っていれば、解釈レイヤはいつでも作り直せます。逆にrawを解釈で上書きしてしまうと、解釈を間違えたときに後戻りできません。Personal Context Hub も、生データの保全と、その上の派生・集計レイヤを分けた構造で作っていて、rawはそのまま残しつつ、日次の集計はそれとは別のテーブルに組み立てています。
レイヤとして整理すると、こうなります。
データの4レイヤ:raw(append-only の証拠)→ event(出来事単位)→ aggregate(集計)→ interpretation(Agent の解釈)。下ほど証拠で不変、上ほど解釈で作り直せる
大まかには、rawをappend-onlyの証拠として残し、そこからevent・aggregate・interpretationの順に、再生成できる解釈レイヤを重ねています。
これは前回の記事 で書いた「集めること自体ではなく、Agentに渡せるコンテキストを準備することが目的」の、いちばん地味で泥臭い部分です。横断の価値は、きれいに揃ったデータからではなく、揃っていないデータをどう束ね直すかから出てくる、というのが今のところの実感です。
どこまでを基盤に置き、どこからをAgentに置くか
この構成では、データ取り込み、定期実行、監視、バックアップのような処理は基盤側に置いています。
一方で、文脈の解釈、複数データの意味づけ、提案文の生成、対話はHermes Agent側の役割です。
ルールは単純で、LLMの判断が不要なものは基盤側へ、文脈理解が価値の中心になるものはAgent側へ寄せています。
基盤側と Hermes Agent 側の責務分担:取り込み・定期実行・監視・バックアップは基盤、文脈の解釈・意味づけ・提案・対話は Agent
ただ、この境界の設計は実際にはかなり難しいです。「文脈の解釈」「複数データの意味づけ」といっても、その中には決まりきった部分とそうでない部分が混ざっています。睡眠ログから就寝・起床を切り出す、滞在地から移動と滞在を判定する、といった意味づけはルールが決まっていてLLMの判断を要しません。こうした決まりきった意味づけは基盤側に下ろし、前述のevent単位のログとして持たせています。Agent側に残すのは、その上での開かれた解釈——複数ドメインを跨いで仮説を立てたり、その日の状況に合わせて提案を組み立てたり——の部分です。
線引きの基準は「LLMの判断が要るか」ですが、何が"決まりきっている"かは運用しながら動きます。実際によくやるのは、新しいデータソースや使い方を、まずAgent側に繋いで試すことです。そして、どんなユースケースで使うか・どんなデータ型が要るかが見えてきたら、その部分を基盤側に下ろします。最初はAgentにアドホックに取得・解釈させ、形が固まったら、収集とテーブル、必要ならAPIとして基盤に固定する。立ち上げは軽く、固まったものは安定して回せる、という進め方です。図ではきれいに左右へ分けていますが、現実にはこの境界を行き来させながら引いている、というのが正直なところです。
この構成で生まれる体験
この基盤があることで、Hermesは単なるチャット相手ではなく、個人データを踏まえた伴走者として振る舞えるようになります。ただし、現時点でこの構成から実際に生まれている体験として紹介したいのは、まず旅行の振り返りです。前回の記事 で思想として書いた「旅行記」に、この実装でどこまで近づいたか、という話でもあります。
旅行の振り返り
旅行後は、Dawarichの位置情報、Immichの写真、Obsidianに残したメモを横断して、日ごとの行動ログを組み立てます。どの日にどこへ行き、どの写真がその場面に対応するかを、あとから復元しやすくなります。MoneyForwardの支出も合わせれば、移動・写真・支出を同じ旅行の流れとして見直せます。
下の画像は、実際にHermesに「3月のタイ旅行の旅程とその時の写真をURL付きで教えて」と依頼したときの出力例です。Hermesはまず、対象の旅行を2026/3/18〜3/22のタイ旅行として特定し、Immichから該当期間の写真を日別に抽出しています。出力では、合計451件の写真が、3/18は27件、3/19は170件、3/20は112件、3/21は138件、3/22は4件という粒度で整理されています。
そのうえで、Dawarichの位置情報、GPS付き写真、メモに残した手がかりを突き合わせ、日ごとの旅程として文章化しています。たとえば、3/18はドンムアン到着からバンコク市内への移動、3/19は王宮・Wat Pho・Wat Arun周辺からICONSIAM方面への移動、というように、その日の流れがまとまります。さらに各日には代表写真のImmich URLが付くので、文章で旅程を読み返しながら、対応する写真へすぐ戻れます。
ここでやりたいのは、AIに旅行記を創作させることではありません。位置情報、写真、メモという別々の一次ソースを同じ時間軸に寄せ、「その日はどういう日だったか」を復元しやすい形に並べ直すことです。単に写真を時系列に並べるだけではなく、移動・滞在・代表写真を合わせて、あとから読める旅行のまとまりにすることを狙っています。
Personal Context Hubから情報を引き出した3月のタイ旅行のまとめ
運用と再現性
基盤とHermes Agentは、自宅のLinuxマシン1台の上で動かしています。Postgres・Redis・各収集アプリ・バッチジョブ・監視を同居させ、異常時はSlackに通知する構成です。個人用途としては大きめですが、クラウドで分散運用するより管理対象を減らしやすいと感じています。
secrets 。APIキーや認証情報はSOPS + ageで暗号化してリポジトリに置き、起動時にsops exec-envで環境変数として注入します。少なくとも、起動に必要なenv secretは平文でリポジトリに置きません。一方で、ブラウザログインのstorage stateやCLIの認証キャッシュのように、実行時にローカルへ残るものはあります。そうしたsession/cache類はgitignoreし、権限を絞ったローカルファイルとして扱う方針です。
監視 。コンテナの死活はsystemdタイマーで定期チェックし、状態が変わったときだけSlackに通知します。データの欠測や遅延はGrafanaのアラートで拾い、各コンテナのログはLokiに集約しています。欠測検知のような定型の見張りにLLMは使いません。
監視・通知の流れ:postgres-shared と Loki を Grafana が読み、ダッシュボードでの可視化と欠測・遅延アラートの Slack 通知、コンテナ死活は check-containers が状態変化時のみ Slack 通知
バックアップ 。resticでPostgresのダンプとデータディレクトリを別ホストへ自動退避しています。ただバックアップは取るだけでは意味がないので、定期的に別ディレクトリへrestoreしてdumpが読めることを確認したいのですが、そこはまだ運用に乗せきれていません。現時点で最大の課題はオフサイトバックアップで、災害や物理故障まで考えるとまだ改善の余地があります。
ネットワークとアクセス 。LAN内のサービスは*.home.arpaの内部ドメインを引き、nginxのリバースプロキシがHostヘッダを見て後段に振り分けます。外部にポートは開けません。家の外から使うときは、ホストもiPhone・Macも同じTailscaleのtailnetに参加させ、tailnet内からのみ到達できるようにしています。位置記録のDawarichのように、iOSアプリがHTTPS(ATS)を要求するサービスは、Tailscale Serveで証明書付きのHTTPSとして終端し、tailnet内のFQDNで配信します。グローバルな口は開けず、必要な端末だけがtailnet越しに届く——データを手元に置いたまま外からも使うための線引きです。
ネットワークとアクセス:インターネットにポートは開けず、外部からは Tailscale の tailnet 経由のみ。LAN 内は AdGuard と nginx で *.home.arpa を名前ベースに引き、Dawarich は iOS の ATS 対応で Tailscale Serve の HTTPS で配信
再現性 。上流のOSS(Dawarich・Immichなど)は、自前のcomposeで設定・ポート・環境変数を明示し、公式imageやDockerfileのbase imageを固定して運用しています。公開用の参照実装では、upstream repositoryをgit submoduleとして取り込むのではなく、composeから利用するimage tagやbuild時のbase imageを指定する形にしています。ここで効いているのが、upstreamの中身には手を入れない、という方針です。設定変更はすべて自分のcompose側で行い、必要な拡張は自前のDockerfileや周辺スクリプトに閉じます。こうすると公式パッケージの恩恵を受けながら、upstreamの更新に追従するコスト(フォークの保守やマージ衝突)を最小に抑えられます。イメージタグは固定し、latestは使いません。構成をコードとして固定しておくと、Agentを乗り換えても、壊れて作り直すときも、同じ状態を再現しやすくなります。
参照実装
ここで書いた構成は、参照実装としてGitHubで公開しています(yutashx/personal-context-hub-concept )。IP・ホスト名・秘密情報はplaceholderに置き換えた、そのままでは起動しないデモ用リポジトリです。起動手順の完成品というより、構成・命名・責務分担・compose配線・運用上の判断を読むためのsnapshotとして切り出しています。本記事で触れたdatalake-apiや各stackの構成は、このrepoで具体的に追えます。
実際に手元で動かしているものは、ここで公開しているデモより少し先を行っています。実運用専用のbackup script、cron / systemd timer、外部サービスのsession/cache、一部のbackfill jobは含めていません。構成の考え方が伝わる範囲を切り出して置いています。
今後の展望
今後は、この基盤の上で旅行の振り返り以外の体験も育てていきたいと考えています。たとえば「今日どう動くべき?」と聞くと、当日の予定・前夜の睡眠・最近のメモ・室温などを合わせて、その日の前提を短く返す朝のブリーフがあります。睡眠が短く午前に会議が集中している日は、「午前は判断の重い作業を避け、午後の外出前に軽いタスクを寄せる」といった提案ができるはずです。
また、睡眠・移動・室温・支出・メモが同じ時間軸で並ぶと、「出社が続く週は睡眠が短い」「室温が高い日は集中が落ちやすい」といった生活パターンの仮説も立てやすくなります。大事なのは、AIに結論を丸投げすることではなく、自分では見落としがちなパターンを候補として出してもらうことです。ここは前回の記事 で書いた「人間が主役、AIは提案者」とも重なります。
まとめ
Personal Context Hub の実装で目指しているのは、すべてを1つに押し込むことではなく、Hermes Agentが個人データへ少ない経路で到達できるようにすることです。
そのために、横断分析に使う主要データはPostgresに寄せ、写真やメモのように一次ソースを保った方が自然なものは別経路のまま扱っています。
そして、取り込みや監視のような定型処理は基盤側に、解釈や提案はAgent側に分けています。
この基盤の上でまず形になっているのは、旅行の振り返りです。朝のブリーフや生活パターンの検出のような提案系の体験は、今後もう少し育ててから改めて紹介できればと思います。ただ、その上で動くAgentがいまHermesであること自体は、本質ではありません。基盤さえ手元にあれば、Agentは時代に合わせて選び直せる——それがPersonal Context Hubを手元に持つ一番の狙いです。
関連記事