ローカルAIの知識保管庫をイメージした抽象的なテックビジュアル

Article

プライベート AI Google Drive を Go + SQLite で自作した話

2026年4月18日
8 min read
#AI#Go#SQLite#Atlas#ローカルAI

家族の記録、子どもの学校書類、医療関係の書類、日々のジャーナル——気づけばこれらが Dropbox と Google Drive と iCloud に散らばっていた。「あの書類どこに入れたっけ」と毎回探す。クラウドの検索は弱いし、AI に「去年の娘の医療費まとめて」と頼もうにも、そもそもデータが渡せない。

これが Atlas を作り始めた動機だ。


なぜ自作したのか

市販のクラウドストレージへの不満は主に 3 点だった。

プライバシー: 子どもの学校書類や家族の医療記録を Google のサーバーに置くことへの根本的な不安。Gemini が「写真を分析してサマリーを作ります」と言ってきたとき、正直ぞっとした。

AI との連携: 自分のデータを AI に活用させたい。「去年の娘の矯正歯科の経過まとめて」「塾の入室説明会の資料あった?」——こういう質問に即座に答えてほしいが、クラウドサービスの AI 連携は閉じた生態系の中でしか動かない。自分のデータを自分の AI で使いたい。

検索品質: Dropbox の検索は英語に強くて日本語に弱い。Google Drive は画像の中のテキストを探せるが、ファイル間の関連付けはできない。「塾関係の書類を全部」という横断検索が難しい。


設計思想: ファイルシステムが正ソース

設計で最初に決めたのは 「ファイルシステムが正ソース」 という原則だ。

データベース (.atlas/cache.db) は検索を速くするためのキャッシュに過ぎない。壊れたらファイルから再構築できる。これが絶対条件だった。

理由はシンプルで、AI パイプラインは失敗する。ネットワークが切れる、モデルがクラッシュする、バグが潜んでいる。そのたびに「DB とファイルどちらが正しいのか」と悩みたくない。ファイルが常に正しい、これだけ覚えていればいい。

v0.1 から v0.2 への転換で最大の変更はここだった。v0.1 は SQLite に全情報を持たせようとしていた。v0.2 では Markdown + Git が正ソース、cache.db は派生物として完全に位置づけを変えた。


state = 物理フォルダ

ファイルの「状態」を表すのにデータベースのカラムは使わない。置き場所がそのまま状態を表す。

~/Atlas/
├── inbox/          ← とりあえず放り込む。未処理
├── sources/        ← AI 処理済み
│   ├── education/
│   ├── family/
│   ├── journals/
│   └── ...
├── entities/       ← 人物・組織・場所のまとめページ
├── concepts/       ← トピック・テーマのまとめページ
├── .sources/       ← 元ファイル (PDF・画像) の保管場所
└── .atlas/
    └── cache.db    ← FTS5 + embeddings (派生物)

inbox/ にあるファイルは未処理、sources/education/ にあれば「教育関係・AI 処理済み」。状態をクエリするのに SQL を書く必要がない。ls inbox/ で現在の処理待ちが全て分かる。


frontmatter でメタ情報管理

全ての Markdown ファイルは YAML frontmatter を持つ。これが「ファイルシステムが正ソース」の実装だ。

---
id: 550e8400-e29b-41d4-a716-446655440000
type: source
source: /.sources/塾の入室説明会.pdf
tags: [education, 学習塾, 娘]
summary: 近隣の学習塾の入室説明会資料。2026年4月入室テストのスケジュール、月謝、授業形式について記載。
entities:
  - name: 娘
    type: person
  - name: 学習塾
    type: organization
events:
  - title: 塾の入室テスト
    start: 2026-04-12T10:00:00+09:00
created: 2026-04-07T10:00:00+09:00
updated: 2026-04-07T10:00:00+09:00
---

# 学習塾の入室説明会(2026年4月)

## 概要
近隣の学習塾の入室説明会に参加した。...

cache.db が壊れても、この frontmatter があれば完全に再構築できる。これが核心だ。


技術スタック

Go シングルバイナリを選んだ。理由は単純で、Mac Studio に常駐させるサービスとして Python や Node.js の依存管理をしたくなかった。go build で吐き出された単一バイナリを launchd に渡す——それだけでいい。

atlas-api        ← HTTP サーバー + 処理キュー
atlas-indexer    ← cache.db 再構築・FTS 更新
atlas-batch      ← 夜間バッチ (人間の編集検出 → AI 更新)

SQLite は FTS5 全文検索と sqlite-vec によるベクトル検索を使う。外部サービス不要で、シングルファイルでバックアップも楽だ。

-- FTS5 全文検索
CREATE VIRTUAL TABLE files_fts USING fts5(
    title, tags, content,
    content=files, content_rowid=rowid
);

-- ベクトル検索 (sqlite-vec)
CREATE VIRTUAL TABLE embeddings USING vec0(
    file_id TEXT PRIMARY KEY,
    embedding FLOAT[1024]
);

検索は FTS5 + ベクトル検索を RRF (Reciprocal Rank Fusion) で融合するハイブリッド方式を取っている。「娘の矯正歯科」のような正確なキーワードは FTS5 が拾い、「去年の娘の健康記録」のような意味的クエリはベクトル検索が補う。

さらに entities/concepts/ のハブページを最優先にするランキングを入れた:

GET /api/v1/search?q=娘
  ├── Phase 1: entities/娘.md が最上位 (ハブ優先)
  └── Phase 2: 娘に言及する source 全件 (FTS + Vector RRF)

AI パイプライン

inbox にファイルを放り込むと、非同期で Gemma 4 31B (ローカル MLX) が処理する。

inbox/塾の入室説明会.pdf を投入
       ↓ POST /api/v1/process (クライアントがトリガー)
       ↓ Gemma 4 31B (ローカル MLX, port 11434)
       │
       ├─ sources/education/塾の入室説明会.md を生成
       │    summary / tags / entities / events を frontmatter に書き込む
       │
       ├─ entities/娘.md を更新 (関連ドキュメントを追記)
       ├─ entities/学習塾.md を更新 or 新規作成
       ├─ concepts/中学受験.md を更新 or 新規作成
       │
       ├─ 元ファイルを .sources/ に移動
       ├─ inbox/ から元ファイルを削除
       │
       └─ git commit (Author: atlas-ai)
            "ingest: 塾の入室説明会.pdf → sources/education/"

AI の出力はファイルに書き込まれる。DB には書かない。AI パイプラインが中断しても、処理済みの Markdown は残る。

Git の author を atlas-ai にするのはポイントで、夜間バッチが「人間が書いた差分」を AI に食わせるとき、atlas-ai のコミットは除外する。自分が書いたものを自分で再処理する無限ループを防ぐためだ。


Chat API: tool-calling エージェント

検索するだけでなく、自然言語で質問に答える Chat API も作った。

POST /api/v1/chat
{ "message": "去年の娘の矯正歯科の記録まとめて" }

内部では tool-calling エージェントループが回る:

1. LLM がクエリを受け取る
2. search_hybrid tool を呼ぶ → 関連ファイルを取得
3. get_file tool で内容を読む
4. 必要なら追加検索
5. 最終的に日本語で回答を生成

Tool は 6 つ用意した:

Tool 役割
search_hybrid FTS5 + ベクトル融合検索
get_file ファイル本文取得
list_recent 最近のファイル一覧
get_metadata frontmatter 取得
patch_file ファイル更新
update_metadata frontmatter 更新

クライアント

iOS アプリ (SwiftUI) と TUI クライアント (BubbleTea) を作った。どちらも HTTP API 経由で、クライアント側にロジックはない。

iOS からの典型的な使い方:

  1. 書類をカメラで撮影、inbox にアップロード
  2. 「処理」ボタンをタップ → AI が自動でメタ情報を抽出
  3. Chat FAB から「この書類について聞く」
  4. 回答と引用元のリンクが返ってくる

インフラ: Mac Studio 常駐

Mac Studio (M4 Max 64GB)
├── atlas-api        (launchd, port 8081)
├── MLX Gemma 4 31B  (launchd, port 11434)
└── MLX e5-large     (launchd, port 11435, embeddings)

Tailscale
└── 家族の iPhone からどこからでもアクセス

launchd の plist を書いておくと Mac を再起動しても自動で起動する。監視コマンドを定期的に叩いて生きているか確認する、という管理が不要になった。


アーキテクチャ図

外部クライアント (iOS / TUI / n8n / Telegram)
  │
  │ Tailscale
  ▼
┌─────────────────────────────────────────────┐
│ Mac Studio                                  │
│                                             │
│  atlas-api (port 8081)                      │
│  ├── /files    CRUD + 検索                  │
│  ├── /process  AI 処理キック                │
│  ├── /chat     tool-calling エージェント     │
│  └── /events   カレンダー連携               │
│                                             │
│  MLX Gemma 4 31B (port 11434)               │
│  MLX e5-large embed (port 11435)            │
└─────────────────────────────────────────────┘
  │
  ▼
~/Atlas/  (ローカル SSD・Git リポジトリ)
├── inbox/           ← 投入口
├── sources/         ← AI 処理済み Markdown
├── entities/        ← 人物・組織・場所
├── concepts/        ← トピック・テーマ
├── .sources/        ← 元ファイル (PDF・画像)
└── .atlas/
    └── cache.db     ← FTS5 + embeddings (派生物)

実際の運用例

平日の朝、学校からのプリントを iPhone でスキャンして inbox にアップロードする。「処理」ボタンを押して Atlas に手渡す。30 秒ほどで Gemma が summary と tags と entities を生成して sources/education/ に分類する。

週末に「今月の娘の学校行事まとめて」と Chat に打ち込むと、カレンダーの events frontmatter を拾って一覧を返してくれる。

書類を探す時間がほぼゼロになった。それだけで作った甲斐があった。


まとめ

  • ファイルシステムが正ソース — DB が壊れても困らない設計
  • state = 物理フォルダ — パスを見れば状態がわかる
  • frontmatter でメタ情報管理 — Markdown ファイル単体で完結
  • Go シングルバイナリ + SQLite — 依存なし、運用が楽
  • Gemma 4 ローカル — プライバシー完全保護、完全オフライン動作

プライバシーとデータ主権を保ちながら AI を活用したい人には、自作が現時点の最善解だと思っている。クラウドサービスが「あなたのデータをサーバーに送らずにローカルで AI 処理」を本気で実現するまでは。