Personal OS / 個人データ基盤 — 構想・設計・実装の現在地

PersonalOS 設計図

2026-10-06 追記:iPhone のショートカットで書いたメモを iCloud Drive 経由で life に取り込む仕組み(メモ取り込み)は、仕組みごと廃止して削除した(shika の指示)。下の図と説明に出てくるメモ取り込みは、経緯の記録として残している。健康データの取り込みは別の仕組みで、廃止の対象ではない。

これは「AIに自分の記憶を預けるシステム」ではない。自分自身のデータ基盤を作り、AIをそのインターフェースとして使うシステムである。使うAIが Claude から別のモデルに替わっても、10年後に全く別のAIになっても、データは自分のものとして残る。

索引チャンク
10,073
索引ファイル
362
索引サイズ
28.6MB
検索(実測中央値)
21ms
本人が手を動かす残作業
66分
稼働デーモン
3本

本書で使う用語(最初に1回だけ定義する)

データリポジトリ life/
利用者の個人データ(日々の記録・タスク・家計・健康・目標・プロジェクト文書など)を Markdown で一元管理している Git リポジトリ。ディレクトリ名がそのまま life/ なので、パス表記ではこの名前をそのまま使う。
本人
このシステムの利用者であり、データの所有者。「本人でなければできない作業」を 👤 本人 で示す。
AIエージェント
本人が対話に使っている AI コーディングエージェント(Claude Code)。データリポジトリのファイルを直接読み書きできる。本書で「AI」と書いたときはこれを指す。
出先メモ
iPhone から短いメモを投げ込む経路。iPhone のショートカット(名前 life-memo)が iCloud Drive の受信フォルダ(life-inbox/)にファイルを置き、Mac 側が取り込む。
正本 / 派生物
正本=人が編集する唯一のデータ(Markdown、Git 管理)。派生物=正本から機械的に作り直せるもの(索引など)。
索引 / チャンク
索引=検索用に Markdown を分割して SQLite に入れたもの。チャンク=その分割単位(見出しごとの本文断片)。
launchd
macOS 標準のジョブスケジューラ。本書の「デーモン」「ジョブ」はすべて launchd が一定間隔で起動するスクリプトを指す。
TCC / FDA / ADP
TCC=macOS のプライバシー保護機構(iCloud Drive などへのアクセスをアプリ単位で許可制にする)。FDA=フルディスクアクセス(TCC の許可の1種)。ADP=iCloud の「高度なデータ保護」(エンドツーエンド暗号化)。
識別子(M-1・N-3・S-1・IG-1c・J-1・Q-6 …)
原典文書の項目番号。原典へ辿れるように本書でもそのまま使う。M=取り込みスクリプトの規定、N=文書規約、P=データの器の要求、S=結合の継ぎ目、IG=検証項目、J・Q=本人の判断待ち事項。

原典の文書群は独自の記号体系を持つ。採らなかった設計を消さずに、なぜ採らないかごと残すのが作法になっている。以下の記号はそのまま本ページでも使う。

🔴 静かに壊れる
エラーを出さずに失敗する箇所
⚠️ 代償
得るために何かを失う
⭐ 非自明
実測して初めて分かった
✅ 実測済
叩いて確かめた
⬜ 未確認
まだ測っていない
👤 本人
AIエージェントが代行できない
01 — 最終ゴール

自分についてAIに聞けば、自分自身より自分の過去を正確に把握している

これが構想文書 requirement.md §20 の最終ゴール。単なるメモ置き場ではなく、人生・仕事・思考・健康・プロジェクトのデータベースを作り、AIをそのインターフェースとして使う。

ゴールに到達したかどうかは、次の問いに答えられるかで測る。すべて原文(§20・§15)にある問いである(一人称のみ「自分」に置き換えた)。

自分はなぜこの事業を始めた?
去年と比べて何が変わった?
今の自分が抱えている問題は何?
過去の自分なら、この問題をどう考えていた?
今の生活習慣と仕事の成果に相関はある?
過去に考えたアイデアの中で、いま実行すべきものはどれ?
今の自分がやめるべきことは何?
過去の意思決定と現在の状況を踏まえて、次に何をすべき?

三層 — Memory / State / Activity

§16 の最終形。3つの層がすべて AI Retrieval(AIによる検索・取得の層)を経由して、Claude でも自作AIでも同じように読める状態にする。

MEMORYSTATEACTIVITY 思考 / アイデア知識 / 調査結果意思決定(Decision) 健康(体重・PFC・睡眠)資産・家計目標 プロジェクトタスク作業ログ AI RETRIEVAL Claude(対話)AIエージェント(Claude Code)将来の自作AI
三層はデータの性質で分かれている。Memory は書き換えない思考の堆積、State はいまの状態、Activity は進行中の仕事。AIはこの3つを横断して初めて「過去の意思決定と現在の状況を踏まえて」答えられる。PFC=タンパク質・脂質・炭水化物の摂取量。

データの3分類 + 意思決定を別格に扱う

分類中身例(原文)置き場所
Fact事実・測った値体重 76.2kg/歩数 9,832/摂取カロリー 2,143kcal数値。人が直接編集する必要がない
Knowledge知識・調査結果SupabaseでRLSを設定する方法/React Server Componentsの調査結果Markdown
Thought自分自身の思考「自分は本当に法人化したほうがいいのか?」Markdown。第二の脳ではこれが最重要
Decision意思決定(別格)Decision / Reason / Status の3点セットで残すMarkdown
Event時系列の出来事{"type":"idea.created","source":"iphone",…}MVP外。着手時に「索引の対象か」を必ず決める
requirement.md §10〜§12。Decision を別格にする理由は「なぜこの判断をしたんだっけ?」に後からAIが答えられるようにするため。
02 — 設計の芯

器の名前ではなく、3つの要求を守る

「Markdownを正本にする」は形式へのこだわりではない。守りたいのは次の3つで、これを満たすなら器(データの格納形式)は何でもいい。逆に満たさない器は、名前がMarkdownでも失格になる。

#要求これを満たさない器の例
P-1grepできる(AIがファイル読み込み・全文検索・パターン一致という基本操作だけで確実に読める)DBの中の行
P-2差分が安定している(git diff が人に読める)1行に全部入ったJSON
P-3人が直接編集できるバイナリ・専用エディタが要る形式
⭐ この3つで測ると、結論が変わった

構想文書が PostgreSQL に置こうとしていた5件(Event Log / Decision / Entity / タグ / 計測)のうち、PostgreSQLを必要とするものは0件だった。逆に、3つ全部を必要としないものもある — 計測データ(体重・PFC)は P-3 を必要としないので Markdown の外に出せる。「全部Markdown」でも「全部DB」でもない。

索引が要る理由は「意味の理解」ではない

コードを1行も書かず grep と sed だけで本人の問いに答える実験(原典の呼称 PT-R0)を実際に走らせた。2問とも通った。それでも索引は要る。ただし理由が変わった。

対象コーパス(検索対象の全文書) 剪定後(パスとヒット数だけで絞った) 実際に読んだ量 4,288,231 B 277,895 B ≈ 26,000 B 165分の1に剪定した — これを毎回、人が手作業した
剪定(候補の絞り込み)に使った情報は「パス」と「ヒット数」だけだった。索引が自動化すべき仕事は意味の理解ではなく、この剪定である。手作業した3つのコマンドが、そのまま索引の domain(第1階層ディレクトリ)/ heading_path(見出しの階層)+start_line(行番号)/ 既定除外(ノイズになるディレクトリを最初から外す)に対応していた。
🔴 通った理由は、設計ではなく書き方だった

実験が通った理由は3つで、どれもベクトルでも索引でもない。①パスとファイル名が意味を持っている(finance/simulations/2026-03-27_incorporation-simulation.md)/②見出しが自己説明的(666KBのファイルでも grep -nE '^#{1,3} ' で253本の目次が出て当たりが付く)/③語彙が一貫している(「法人化」に表記ゆれが無い)。

これは設計の成果ではなく、本人がこれまでデータリポジトリを書いてきたやり方の成果である。したがってこの3つは「システムが提供するもの」ではなく「システムより先に守るべきもの」。日付をパスに入れる/見出しを内容の名前にする/同じものを同じ語で呼ぶ — これをやめると、どんな索引を作っても剪定が効かなくなる。

正本と派生物の境界

設計の骨格はこの1本の線に集約される。正本は git の中に、派生物は git の外に置く。索引をデータリポジトリの外に置く決め手は「重いから」ではない。

正本 — life/ ・ git 追跡下 business/ docs/ finance/ goals/ health/ mental/ pokemon/ projects/ research/ sessions/ tasks/ notes/ 🆕 出先メモの着地先 bin/ 🆕 この Mac 用の運用スクリプト 既存 355ファイル / 12,652,154 B は1バイトも動かさない 新設はリポジトリ直下に2つだけ 派生物 — リポジトリの外 ・ git に入れない ~/.local/share/personal-os/index/ second-brain.sqlite ~/.local/state/personal-os/logs/ ~/.local/state/personal-os/ingested/ ~/Library/Mobile Documents/…/life-inbox/ 索引の text 列はリポジトリ全平文の複製である git 履歴に入ると filter-repo なしでは消せない index build いつでも作り直せる 戻さない 帰結が非対称 — git混入は取り返しがつかない/索引の取り違えは無損失
この非対称性が置き場所を決めている。.gitignore に索引の行が無いのは書き忘れではなく、「行が無いことが正しい」状態(索引はそもそもリポジトリの外にある)。ログをリポジトリの外に出したのも同じ理由で、logs/(再生成可)と events/(再生成不能)が隣り合うと、片方を ignore した人がもう片方も同じだと判断して事故が起きる。左枠のディレクトリ名は既存の第1階層(ドメイン)で、business=事業、finance=家計・資産、goals=目標、health=健康、mental=気分ログ、pokemon=趣味の対戦記録、projects=プロジェクト文書、research=調査、sessions=AIエージェントの作業記録、tasks=日次タスク。
03 — データフロー

iPhone のメモが検索に載るまで

MVP で作るものは4つしかない。①iPhoneのショートカット(7アクション)②Macの取り込みスクリプト(launchd・5分おき)③チャンク表を作る index build ④それを LIKE(SQL の部分文字列一致)で引く search CLI。言語モデルは1回も呼ばない。ベクトル検索も全文検索エンジンも常駐プロセスも、MVPには無い。

TCC 保護領域(要 FDA) life/ ・ git 追跡下 iPhone iCloud Drive notes/ SQLite 索引 ショートカット life-memo (背面タップで起動) life-inbox/ 2026-09-02-081203-8317.md 2026-09-02.md (日次・追記のみ) ~/.local/share/personal-os/ index/second-brain.sqlite ローカルに書く オフラインでも成功 取り込み 分割 launchd — 常駐せず、間隔で起きる メモ取り込み 300s → bin/ingest-notes.sh 索引更新 900s → bin/index-refresh.sh HealthKit / 食事記録アプリ 監視ディレクトリ health/metrics/ 体重・歩数・睡眠・PFC ~/.local/state/personal-os/ inbox-health/ ← 保護領域の外 2026-09-04.md (1日1ファイル・追記) 健康データ取り込み 3600s ・ パーサ未実装 AIエージェントのシェル実行 search "法人化" --since 2026-01 MCP サーバは立てない LIKE 21ms
設計の芯は「常時起動の受け口」ではなく「iPhoneのローカルに先に書く」こと。一発勝負になるのは受け口が寝ているからではなく、キャプチャの成功がその瞬間のネットワークに依存するから。だから Mac への直接POST も Supabase も自前サーバも、出先メモという用途では iCloud Drive より悪い — 電波が無い場所で無音で失敗する。「食事記録アプリ」は本人が使っている「あすけん」を指す。

稼働中の launchd ジョブ3本

✅ 実測済 3本とも登録・稼働中、直近の終了コードは 0(2026-09-04 観測)。ログを3本に分けるのは、同じファイルに混ぜると検知器(ログを監視する仕組み)が黙って別のものを数え始めるため。

役割ジョブのラベル間隔実体ログ処理
メモ取り込みcom.shika.life-inbox300sbin/ingest-notes.shlogs/ingest.logiCloud life-inbox/ → notes/
索引更新com.shika.life-index900sbin/index-refresh.shlogs/index.logindex build の定期実行(詳細: 索引ジョブ)
健康データ取り込みcom.shika.life-health3600sbin/ingest-health.shlogs/health.log監視ディレクトリ → health/metrics/(⚠️ パーサ未実装)
ラベルは launchd 上の登録名(本人の環境固有の命名)。ログの置き場所はすべて ~/.local/state/personal-os/logs/(リポジトリの外)。ファイル変更イベント(WatchPaths)を iCloud Drive に張らず StartInterval の定期ポーリングにしたのは、「変更イベントが飛ばなかった」という失敗モードを構造的に存在させないため。

取り込みスクリプトが満たす規定 — M-1〜M-8

この8つは「気をつける」では守れないので規定として固定されている。各行の右側が、その規定が無いときに起きる事故である。

#規定何を防ぐか
M-1最初に受信ディレクトリを ls し、失敗なら ERROR を1行書いて非ゼロで終わる🔴 TCC で読めないとき「0件だった」と区別がつかない
M-2更新時刻(mtime)が60秒以内のファイルはスキップiPhoneが書いている最中/同期中のファイルを途中まで読む
M-3サイズ0、または --- で始まらないファイルは移動せずスキップし WARNiCloud が未ダウンロードのプレースホルダを空メモとして取り込む
M-4順序を固定 — ①追記 → ②読み直して確認 → ③そのあと退避先に移動すると、追記に失敗した瞬間にメモが消える
M-5~/.local/state/personal-os/ingested/ へ移動する。削除しない取り込みバグに気づいたときやり直せる。削除は本人が明示的にやる
M-6書式は固定。frontmatter(ファイル先頭のメタデータ)は notes/ に持ち込まない(captured_at を見出しに変換して捨てる)notes/ がリポジトリで最も frontmatter の多い場所になるのは、あとから入れた仕組みの都合でしかない
M-7毎回ログに1行書く(0件でも書く)🔴 無音で死んだデーモンと、メモが無い日を区別する唯一の手段
M-8git add / git commit を絶対にしない機械はコミットしない。⭐ 副産物として notes/ の変更が必ず人の目を通る
🔴 set -euo pipefail を意図的に採らない

リポジトリ内の既存スクリプト(別プロジェクトの同期処理 sync.sh)は set -euo pipefail である。揃えないのは慣行を無視しているからではなく、仕事の性質が違うから。sync.sh は「失敗したら止まってよい」仕事、取り込みスクリプトは「失敗を記録してから止まる」必要がある仕事(M-1)。

実測: set -e; ls /nonexistent 2>/dev/null; echo ERROR…; exit 3 は ERROR を1行も出さずに exit=1 で死ぬ。ログには何も残らない。

詳細: phase1-implementation.md §9(手順8 — 取り込みスクリプト・plist・回帰テスト)

04 — ステータス遷移

メモ1件が通る状態と、静かに落ちる分岐

正常系は6状態。重要なのは分岐のほうで、赤い枝はどれもエラーを出さずに止まる。だから検知は「エラーが出ないこと」ではなく「1行必ず出ること」で行う。

S1S2S3 S4S5S6 iPhone ローカルに書けた Mac 側に同期で現れた notes/YYYY-MM-DD.md に追記された 読み直して内容を確認した ingested/ に退避した 索引に載った → 引ける life-inbox/<日付-時刻-乱数>.md mtime 60秒以上前になるまで触らない(M-2) JST 暦日で振り分け M-4 の順序。ここを逆にするとメモが消える rm しない(M-5) index build 再実行までは載らない(静的) iPhone の暦法が和暦 → ファイル名が 0008-09-02 FDA が効いていない → 0件のまま処理完了 shebang が /bin/sh → 毎回空振り 途中で死ぬ → 追記済み・未退避 拡張子が .txt に化ける(検証項目 IG-3b) 月次ファイルにすると doc_date が全件 NULL エラーなしで ok=0 が続く。WARN 行のみで検知 「0件だった」と区別がつかない → M-1 が要る理由 /bin/sh は別 inode・別 code signing。POSIX モード 次回二重追記。本文込み完全一致検査で防止 未ダウンロードの実体なしファイルも同じ症状 --since からメモだけが黙って落ちる
6本の分岐のうち、5本はエラーを出さない。受け入れ基準が「エラーが出ないこと」を合格条件にしないのはこのため。🔴 「何も出なかった」を合格にしない — 0件でも1行出る設計になっているので、無音は「対象が0件」ではなく「死んでいる」の可能性がある。FDA は TCC の許可がスクリプトの実行主体(/bin/bash)に付いていないと効かない。

閉じていない継ぎ目 — 個別には全部通るのに、繋ぐと落ちる

#継ぎ目状態
S-1search ↔ index build(スキーマ契約)🔴 1度実測で破れた。search が SELECT k, v FROM meta を読み、テーブル定義は meta(key, value) だった。本物の索引に対して no such column: k で落ちた。是正済みだが契約が4件まだ未確定
S-2ingest(取り込み) ↔ index build✅ 唯一つながっている。notes/2026-09-02.md を実際に索引に通し、domain='notes' doc_date_source='path_day' で引けることを実測済み
S-3全部 ↔ 本人の環境(FDA/TCC・iPhone・ADP・本番のパス)⬜ 1件も検証されていない。本人が手を動かすまで測れない
⭐ S-1 が結合検証を独立した工程にする理由

search はダミー索引で受け入れクエリ7本を通し、index build は自分の索引で受け入れ基準5本を通した。両方とも正しく仕事をしている。それでも繋がらなかった。誰の担当範囲にも「繋ぐ」が入っていなかったからである。

したがって手順12(結合検証)は省略可能な確認ではない。これを通していない状態は MVP が完成していない状態である。

詳細: phase1-implementation.md §1(何が既に動いていて、何が未検証か)・§12(結合検証)

05 — 管理フォーマット

文書は2階級しかない — 記録と正本

「全ファイルに日付を付ける」規格は誤りだった。文書を記録(record)と正本(living)の2階級に分け、日付の有無そのものを階級の表現にする。日付を持たないことが正しいファイルが122本(35.0%・実測当時)ある。

記録 — RECORD 正本 — LIVING その時点で起きたこと・測った値 後から書き換えない。追記のみ いま現在の状態・判断・仕様 上書きして最新に保つ sessions/ notes/ tasks/daily/ finance/simulations/ pokemon/battle-logs/ health/metrics/ goals/ docs/ projects/**/docs/ finance/savings-plan.md health/overview.md doc_date = '2026-09-04' doc_date IS NULL NULL は「日付が不明」ではなく「正本である」。 --since は記録を絞る道具であって、正本を落とす道具ではない
この1本の線が、検索の挙動から日付の付け方まで全部を決めている。⚠️ 代償 --format full(本文つき出力)を絞らずに叩くと正本が1件も出ない(並び順が日付のあるものを先に出すため)。回避策は --domain / --path で絞ること。

規約 N-0〜N-9

#規約根拠・実測
N-0文書は2階級しかない(記録/正本)上図
N-1種別と分類はディレクトリで決める。タグにしない。第1階層(=ドメイン)は現在14個で増やさない。深さは4階層以内。ルート直下は README.md 以外を置かないdomain はパス第1階層から自動で付く。⭐ タグの付け忘れが原理的に起きない
N-2日付はパス要素の先頭に置く。<domain>/<種類>/<YYYY-MM-DD>_<topic>.md。最左が勝つ=ディレクトリが勝つ正本には日付を書かない
N-3YYYY-MM(月粒度)を廃止する。日次か日付なしかの2つだけ誤検出が実在(「2026年4月時点のデータを扱う分析文書」が日付と誤読される)。🔴 月粒度は打ち間違いの受け皿 — 2月30日を打つと「2月1日」に化ける
N-4見出しから日付を拾う段を廃止するいまこの段が生んでいる4チャンク・3ファイルは、すべて正本の中の1節(例: health/overview.md の「2026-10-01」は目標日)
N-5拡張子は .md だけ。「拾わなかったもの」を毎回出す実測: .md 以外が132本(json 104・txt 4・ソース12・設定8・画像3・.DS_Store 1・🔴 .env 1)
N-6frontmatter は使わないindex build に frontmatter 解析コードは1行も無い。取り込みデーモンは frontmatter を剥がす。例外は AIエージェントのスキル定義ファイル SKILL.md 15本だけ(外部ツール側の規格)
N-7日付は JST の暦日。date('now')(UTC)を使わない日付比較でズレる
N-8住所を持たないチャンクを作らない(見出しが1つも無い/見出しの前に本文がある は規格違反)heading_path='' を生むと剪定の本体が効かなくなる
N-9本文は NFKC 正規形(Unicode の互換正規化)で書くひらがな/カタカナは NFKC で寄らない。SQLite は ASCII しか大文字小文字を理解しない
note-format-spec.md(2026-09-03・3回の設計レビューを経た確定仕様)。§9 の3件だけ未決。

実際のファイル形式

notes/2026-09-02.md — 出先メモ(記録・機械専有)
# メモ 2026-09-02

> 🤖 このファイルは取り込みデーモンが追記する。人も AI も編集しない。
> 出所: iPhone ショートカット life-memo → iCloud Drive life-inbox/。
> 整理・分類はしない(設計)。

## 2026-09-02T08:12:03+09:00

(メモ本文)
health/metrics/2026-09-04.md — 計測(記録・機械専有)
# 計測 2026-09-04

> 🤖 機械が書く観測の記録。規則・項目・値の意味は README.md。

## 2026-09-04T00:00:00+09:00

| 項目 | 値 | 出所アプリ | 経路 |
| --- | --- | --- | --- |
| 体重 | 未取得 | — | asken→health |
| 体脂肪率 | 未取得 | — | asken→health |
| 歩数 | 未取得 | — | iPhone→health |
… 全10項目(体重・体脂肪率・歩数・睡眠時間・消費カロリー・
   安静時心拍数・摂取カロリー・タンパク質・脂質・炭水化物)
   asken = 食事記録アプリ「あすけん」
  • 運動(ワークアウト)は項目を作らない。Apple Watch を着けているだけでは貯まらず、永久に埋まらない欄になるため。
  • 睡眠時間は導出値。asleepCore + asleepDeep + asleepREM + asleepUnspecified + asleep(廃止値)(HealthKit の睡眠ステージ)。inBed(就床時間)は足さない。
  • 上書きせず追記する。当日・前日の値を Apple が再推定して置き換えることがあるため、同じ日付を後からもう一度取り込んで ## <ISO8601> ブロックを足す。notes/ と違って重複排除しない — 測定値は同じ日付・同じ値が何度でも正当に来る。
  • ⚠️ 人が health/metrics/ に手で書き足したときに検出する仕組みは、まだ無い。

健康データ取り込みの契約(原典の呼称 H-CONTRACT)

転送層(どう Mac まで運ぶか)と整形層(どう Markdown にするか)を分けるための契約。監視ディレクトリを iCloud Drive ではなく ~/.local/state/(TCC保護領域の外)に置いたので、配送手段が変わっても差し替わるのは「落とす側」だけになる。

項目契約
落ち先~/.local/state/personal-os/inbox-health/
ファイル名<source>_<YYYY-MM-DD>_<HHMMSS>.<ext>
例 healthkit_2026-09-04_101500.csv
完了判定mtime が60秒以上前。かつ同名の .part が無いこと
退避削除しない。ingested-health/ へ mv -n
順序①書く → ②読み直して確認 → ③そのあと退避
ログ0件でも必ず1行(run done ok= skip= warn= unknown= err= other=)
git機械は git add / git commit を絶対にしない
⚠️ 集計は索引ではやらない

search は「どのファイルか」を返す道具であって、値を返す道具ではない。チャンクは行を重複させ、列ヘッダを落とし、キーワード一致は母数を欠く(実測: 食事記録28ファイル中 search "タンパク質" が当たるのは19ファイル=32%欠落)。

採る形は正本を直接横断する: grep -rh --include='2026-08-*.md' '^| 体重' health/metrics/。母数の検知器(取れた行数/ファイル数/暦日数)を必ず併記する。

詳細: health-ingest-ops.md(健康データ運用)・note-format-spec.md §1(規格)・§4(移行手順)

06 — 索引と検索

言語モデルを1回も呼ばない索引

index build は純粋なテキスト処理。search は LIKE の部分文字列一致とパス/日付の絞り込みだけ。ランキングを持たない。上位 k=30(約25,000字)をそのまま AI に渡す。索引ジョブの内部(段階・ふるい・分割・差分・スキーマ・失敗モード)は別紙 Markdown 索引ジョブ に詳述してあるので、本節は概要にとどめる。

索引ジョブの概要

  • 索引は原本に追従する側であって、原本ではない。Markdown から一方向に生成する派生物で、失っても作り直せる。→ 索引ジョブ §1
  • スケジューラは薄いラッパーを起動し、7つの段階を通る。途中で降りる出口が3つある(対象なし・変更なし・エラー)。→ §2
  • 対象は4段階でふるいにかける。拡張子(.md のみ)・ディレクトリ名・サブモジュール・バージョン管理の除外設定。→ §3
  • 見出し(H1〜H3)で割り、300字未満は併合、1500字超は分割+200字オーバーラップ。コードフェンス内は見出しとして扱わない。単位は「文字」であってバイトではない(UTF-8 バイト長で実装すると日本語で規則が実質3倍厳しくなる)。実測ではチャンクは最短8字・平均875字・最長1,499字で、「300字未満は併合」は全チャンクを300字以上にする保証ではない。→ §4
  • 2つの関門で書き戻す量を絞る。ファイルの更新時刻と、チャンク本文のハッシュ(chunk_sha)。全件作り直しは1.68秒だが、実運用ではほとんどのファイルが変わらない。→ §5
  • テーブルは2つだけ。本文テーブル chunks と、設定を持つキー・バリュー meta(8つのキー)。chunks の主な列は path/heading_path/start_line/end_line/text/chunk_sha/domain/doc_date/doc_date_source/mtime。ベクトル用の4列(embedding ほか)は最初から空で確保してある。→ §6・§7(各列が誰のどの機能を支えるか)
  • 3本のインデックスが 21ms を 0.35ms にする。ただし効くのは domain = … と path の範囲比較だけで、path LIKE 'finance/%' は全走査(4.9〜5.6ms)になる。→ §8
  • このジョブの失敗はほとんどエラーを出さない。「検索結果が少し減る」という形でしか壊れないので、既知の不整合を一覧で持つ。→ §9
⭐ ベクトルは「作らない」ではなく「MVPの次に作る」

構想文書 §8(pgvector)と実装計画が衝突しているのは「作るか」ではなく「いつ作るか」だけである。スキーマにはベクトル列が最初から NULL で入っている。着手順は ①正解セット10問を作る → ②本人のコーパスと本人の問いでモデルを直接比べる(他人のベンチマークで代理戦争しない)。

なぜ Python 3 で書くのか — 決め手は性能ではない

索引は数秒、検索は21ms。どの言語でも誤差である。決め手は次の実測だった。

$ ls -li /usr/bin/{python3,git,clang,swift}
  → 4本とも同一 inode・118,928 B・リンク数78
$ codesign -dv /usr/bin/python3
  → Identifier=com.apple.dt.xcode_select.tool-shim-public
$ codesign -dv /usr/bin/sqlite3
  → Identifier=com.apple.sqlite3  (これだけ実バイナリ)

/usr/bin/python3 は Python ではなく、xcode-select -p の指す先へ飛ぶ shim(中継用の薄い実行ファイル)で、/usr/bin/git と同一実体である。python3 が消える条件=git が消える条件。データリポジトリは git リポジトリなので、Python 依存は既存のリスクを1バイトも増やさない。しかも壊れ方が loud(stderr+exit=1)で、「黙って空振り」にならない。

🔴 #!/usr/bin/env python3 と書いてはいけない

実測: この Mac には python3 が2つある(PATH先頭=Homebrew 3.14.7//usr/bin/python3=3.9.6)。launchctl getenv PATH は空なので launchd では後者になる。対話シェルで通った 3.14 の構文が launchd の 3.9 で SyntaxError になる。書いた本人は気づけない。

同じ根の問題がシェルにもある: 文字数を数える ${#str} は LANG の有無で値が変わる(実測: 同じ文字列が 12 と 36)。⭐ 「300字」の要求が実装言語の決定にかかっている。

search CLI — 現物のインターフェース

search "法人化 OR 独立" --domain finance,goals --include sessions --since 2026-01 -k 30

出力(--format full):
finance/simulations/2026-03-27_incorporation-simulation.md#法人化の損益分岐 (L112-L149) [finance, 2026-03-27]
  <本文 852字>

必ず出す1行(件数の内訳 — 日付の出所別/ドメイン別):
32件 [日:21 月:6 見出し:3 不明:2]   finance:12 goals:5 sessions:0
オプション意味
queryスペース区切り=AND / OR=OR / "…"=フレーズ / -語=除外
--formatfiles=ファイル一覧+ヒット数(既定・剪定用)/ full=本文 / paths=住所(パス+見出し+行番号)だけ
-kfull/paths で返すチャンク数の上限(既定 30)
--domain / --pathドメイン列挙/パス前方一致で限定
--include既定除外を解除。既定で除外されるのは sessions(AIエージェントの作業記録)・pokemon(趣味の対戦記録)・.claude(AIエージェントの設定)
--since / --untildoc_date の下限/上限(YYYY-MM または YYYY-MM-DD)
--include-undated日付フィルタ時に doc_date IS NULL(=正本)も残す。既定は落とす
--per-file / --max-bytes1ファイルからの上限(既定3)/本文つき合計バイト上限(既定24000)

なぜ LIKE で足りるのか(要点)

全件走査(6,411,071字に対して5語)で中央値 21ms(再現あり 18.8〜20.1ms)。全文検索エンジン(SQLite FTS5)は既定のトークナイザで MATCH "体重" が 🔴 0件・エラーも警告も無し(LIKE なら54件)。⭐ LIKE を採ったことで日本語2文字語の問題が構造的に消えた。FTS5・pgroonga・bigram 分割・多層の検知テスト案のいずれも MVP には要らない。器が SQLite である決め手も検索アルゴリズムではなく、常駐ゼロ・macOS標準・単一ファイルで rm 1回である。

実測値の一覧と索引の効き方: 索引ジョブ §8

⚠️ case_sensitive_like=ON で解決してはいけない

SQLite の LIKE は既定で ASCII の大小を無視する。ON にすると本文検索が大小を区別しはじめ、実測では大文字を含む固有名詞が小文字パターンに NO MATCH になった。直したい箇所とは別の場所が壊れる。

索引の現在のかたち(概要)

合計 10,073 チャンク/362ファイル・28.6MB(2026-09-04 14:39 時点)。内訳は sessions 6,038・pokemon 1,777・projects 1,293 が上位で、sessions と pokemon だけで 77.6% を占めるため既定除外になっている。ただし除外は削除ではない — 過去の問いの答えが sessions/ の中の 666KB のファイルにあった実例がある。

🔴 チャンク数は「一致」を合格条件にできない。同じ対象に測り方の違う数字が複数存在する(7,853 / 8,207 / 8,362 / 8,456 のどれも正しい)。受け入れ基準で数字が「関係式・範囲」で書いてあるのは意図的で、sessions/ が単調増加するので引き直せばまた別の数字になる。

ドメイン別の全内訳: 索引ジョブ §3「索引の中身の偏り」/既知の不整合: §9

07 — 撤回された設計

採らなかったものを、なぜ採らないかごと残す

構想(requirement.md)の技術選定は、実装計画セッションでほぼ全面的に置き換わった。ただし行は消していない。消すと、実装時に同じ案を揺り戻す余地が残るためである。左が初期案、右が決定。

§7 構造化データSupabase + PostgreSQL
決定ローカル完結(Markdown 正本 + SQLite 索引)。理由は費用でもプライバシー観だけでもない — Supabase案はローカル案の上位集合であり、採っても実装物が1つも減らず6件増えるだけだった。独自価値は「Macが寝ていてもiPhoneから検索できる」の1点のみで、本人が「要求しない」と回答したためゼロになった。⚠️ ただし「PostgreSQLに向いているデータ」の列挙自体は有効。否定されたのは器の選択であって、データの分類ではない
§8 意味検索pgvector
⭐ 否定ではなく延期「作らない」ではない。「MVPの次に作る」。スキーマにベクトル列が最初からNULLで入っている。MVPに入れない理由: §15・§20の13問のうち検索層が必須なのは4問で、今日答えられる2問は実演で grep だけで両方通った。器は pgvector とは限らない(ローカル完結が前提のため)
§9 ファイルストレージS3 / R2 / Supabase Storage
需要がゼロ実測: リポジトリ本体が git 追跡しているバイナリは2件・計56KBのみ(あるプロジェクトのアプリアイコン)。⚠️ ただし予防は入れる — .gitignore に *.m4a 等を実績が無いうちに足す(一度履歴に入ると git filter-repo なしでは消せない)。将来「音声メモ」を実装するとき、この節は再び有効になる
§13 ディレクトリ構成second-brain/ の新設
(00_inbox/ 01_me/ 02_projects/ …)
どこにも新設しない既存のデータリポジトリ life/ が本体である。実測: §13の11項目のうち対応物が無いのは 00_inbox/ と 99_archive/ の2つだけ(後者は git履歴が代替)。逆に §13 には health/ も finance/ も sessions/ も無い。sessions/ 単体でリポジトリの .md 行数の 53.9%(84,116行)を占めるので、新設すると「第二の脳」を名乗るディレクトリが本人のデータの半分を含まない状態になる。採る形: 既存355ファイルを1バイトも動かさず、notes/ と bin/ を足すだけ。相対リンク202本も1本も壊れない。移行 約30分
§14 HealthデータHealthKit → 自作iOSアプリ
→ API → Supabase
経路が変わったHealthKit → iOSショートカット → iCloud Drive → Mac の launchd(メモと同じ1本のレール)。⭐ iOSアプリを自作する必要は無い。 ⚠️ 実装時に守ること — 人が書く .md と機械が書くデータを同じディレクトリに混ぜない。混ぜると人の編集と機械の上書きが衝突してエラーが出ずに片方が消える
§5 閲覧UIObsidian+[[wikilink]]
🔺 否定されていない(条件つき)Obsidian は Markdown リンクを一級で扱い、Use [[Wikilinks]] を OFF にできる(一次情報で確認済み)。🔴 ただし vault は life/ に開くこと — second-brain/ に開くとデータの53.9%が外に出る。[[wikilink]] は採らない: 実測でファイル名(basename)が衝突している(リポジトリ中24種類・131ファイル=40.6%。[[dashboard]] は76ファイルのどれかを指す)
検索設計Controlled Vocabulary / タグ表
付与主体が決まらないLLMを呼ばない設計と、人に選ばせない設計に両側から挟まれている。付け忘れたチャンクは検索から静かに消える。パス由来の domain にはこの問題が無い
検索設計Re-ranking の加重和(6項)
係数を調整できない6項中3項のデータが存在せず、正解セットが無い。⭐ 実測では、順位が悪い原因はスコアではなく候補集合の側だった
§15 AI接続MCP サーバ
MVPでは立てないAIエージェントはローカルファイルを直接読めるため、MVPの口は AIエージェント向けの手順書 .claude/skills/search/SKILL.md 1枚と search CLI で足りる。MCP(AIと外部ツールをつなぐ接続規格)を足すとアプリAとアプリBの接点が1本増える。必要になったら足す
受け口の候補Mac への直接POST(Tailscale)
リマインダー / メモ.app
落ちた理由を誤要約しないことMac への直接POSTは「使えないから」落ちたのではない(在席実測6.9日で成立はする)。ADPをONにしたことで、Mac直の唯一の優位(外部送信ゼロ)がiCloud側でも満たされたから落ちた。リマインダー/メモ.app は実測で ~/Library/Reminders も同じTCCの壁だった
⭐ 撤回されずに強化された原則

§19(AIが変わってもデータは自分のものとして残る)は、撤回されるどころか強化された。ローカル完結を選んだ結果、Anthropic にも Supabase にも Apple にも依存しない状態になっている。

各案の検討過程: mvp-implementation-plan.md・searchplan.md

08 — フェーズと進捗

構想の5フェーズと、実際に走っている手順

構想(§18)は Phase 1〜5 の階段だった。実装計画はそれを畳み直し、「iPhoneからメモ → 蓄積 → 検索して引ける」という本人の言葉の MVP を1本道にした。両者はこう対応している。

構想 §18 — 段階的な構築 Phase 1 記録基盤Phase 2 構造化 Phase 3 AI検索Phase 4 自動収集 Phase 5 Agent化 iPhone→Markdown→Git→Obsidian 数値・状態・関係性を DB に Embedding→Semantic Search HealthKit / API→Ingestion Layer 読むだけでなく書く・整理する MVP — いま作っているもの 「iPhoneからメモ → 蓄積 → 検索して引ける」 Phase 1 は丸ごと入る。Phase 3 は「引ける」ところだけ先に取り、意味検索は列だけ空けて後回し。 Phase 2(PostgreSQLへの構造化)は器ごと不要になった — 計測は Markdown、それ以外は索引で足りた。 Phase 4 は土台だけ稼働中 (パーサ未実装) Phase 5 は着手条件つきで保留
Phase の順序そのものは撤回されていない。変わったのは各 Phase の中身の器(Phase 2 の PostgreSQL、Phase 3 の pgvector)と、着手の順番である。「落ちたのではなく、順番が後」という言い方が一貫して使われている。

手順0〜14 — 現在の進捗

上から順に。前が終わらないと次は無駄になる。合計 約3.5〜5時間、うち本人が手を動かすのは 66分。各行の「AI」は AIエージェントが代行する作業、「👤」は本人が実機・GUI を操作する作業。

0前提の実在確認 + リポジトリ外ディレクトリの作成 + 検証ログの掃除AI・5分/すべての前✅ 一部
1/bin/bash に FDA(フルディスクアクセス)を付与👤・5分/⚠️ 代償あり👤 ⬜
2検証項目 IG-1c: FDA が「効いた」ことを確認(付与前後で2回+代償プローブ)👤+AI・15分/🔴 手順8 より前(必須)。落ちたら手順8 に進まない⬜
3ADP(iCloud の高度なデータ保護)を ON👤・10分/⚠️ 復旧連絡先か復旧キーの登録が先。忘れると Apple も復旧できない👤 ⬜
4.gitignore を追記AI・5分/🔴 手順6 より前(必須)。順番を逆にすると履歴から消せない✅
5新設: notes/ + bin/ + CLI の置き場所AI・5分✅
6iPhone: ショートカット life-memo(7アクション)👤・30分/🔴 この節だけは1件も実測されていない👤 ⬜
7検証項目 IG-4: オフライン挙動の実測(機内モードで保存 → 解除 → Mac に現れるか)👤・15分👤 ⬜
8Mac: bin/ingest-notes.sh + テスト + launchd 設定(plist)AI・30〜45分/327行・回帰テスト 19ケース / 51アサーション PASS✅ 実装 ⬜ 本番
9iPhone の背面タップに life-memo を割り当てる👤・3分👤 ⬜
10index build + テーブル定義(DDL)を置いて本番の索引を1回作るAI・20〜30分/885行・本物のコーパスで動作確認済み✅
11search CLI を置くAI・20〜30分/542行・受け入れクエリ7本 0.13秒✅
12🔴 結合検証(search × 本番の索引 × notes/)AI・30分/⭐ MVP 完成条件。実際に落ちた工程である⬜
13AIエージェント向けの検索手順書 .claude/skills/search/SKILL.md を1枚書くAI・10分/対話中の AI が会話の中で過去の記録を引けるようになる⬜ 未作成
14(任意・MVP後)実装コードの git submodule 化 + コミット手順への組み込みAI・20分⬜

各手順の受け入れ基準・所要・検証手順: phase1-implementation.md §2(手順0〜14 の一覧)

⚠️ コードは完成しているが、経路が1度も通っていない

作るもの4つのうち3つは既に動いている。それでも bin/ingest-notes.sh は本物の iCloud life-inbox/ を1度も触っていない(FDA未付与で ls が DENY)。このスクリプトが存在する唯一の理由が、唯一テストされていない部分である。

MVP に入れないもの — 「落ちたのではなく、順番が後」

入れないもの着手条件 / なぜ入れないか
ベクトル検索①正解セット10問を作る → ②本人のコーパスでモデル比較。これが無いと ranking も再ランカーも評価できない
BM25 / FTS5 / pgroongaLIKE が21msで足りる。BM25は「無くても足りる」ではなく「あっても順位を直さない」。着手条件: コーパス10倍かランキングが必要になったら
Query Understanding(問いの解釈層)AIエージェントから使う限りLLMは既に会話の中にいる。足すべきは別のもの(AIを呼べない口のUI)
events/(Event Log)AIエージェントの会話ログ保持期間を10年(cleanupPeriodDays: 3650)に設定したことで会話ログの消失が止まり、「後からでは取り返せない」から「後からやっても同じ」に移った。🔴 未決: events/*.jsonl は索引の対象か。いまの分割器は life/**.md しか見ないので、このままだと events/ は検索から完全に見えない
insights/(AIが生成する洞察の置き場)書き込む主体がベクトル待ちで止まる。生産者がゼロのディレクトリを先に作らない
data/health/*.csv実測: リポジトリに .csv は0件。「CSVのまま」は現状維持ではなく新形式の導入で、health/weight.md からの移行作業が別に要る
旧名ディレクトリ ClaudeCodeClient → PersonalOS の改名動くものが1バイトも増えない。sessions/ 外だけで39箇所+相対リンク8本を壊す(設計の正本文書は今も旧名ディレクトリにある)
09 — 未決の論点

叩いても答えが出ない — 本人の判断が要る事項

実測で決められるものは全部決めてある。残っているのは測っても答えが出ない種類の問いと、その手前で止まっている実機作業である。

いま止まっている 66分

手順所要なぜ代行できないか
FDA の付与5分GUI。システム設定を触る
iCloud Drive に他プロセス製のファイルを置く3分Finder / TextEdit。実測: 対話シェルも AIエージェントも iCloud Drive は Operation not permitted
プローブ(検証用ファイル)の設置・判定12分⭐ AI が代行できる
ADP を ON10分GUI
iPhone ショートカット30分実機
オフライン実測15分実機
背面タップ3分実機
この66分が終わるまで、継ぎ目 S-3(全部 ↔ 本人の環境)は1件も検証できない。

判断が要る事項

#問い選択肢 → 帰結
J-1旧名ディレクトリ ClaudeCodeClient を PersonalOS に改名するかする→名前が実体に合う。39箇所+リンク8本を直す / ⭐ しない(推奨・MVP期間中)→注記だけで要件定義の並走は止まる
J-3Time Machine の宛先を設定するか実測: tmutil destinationinfo → "No destinations configured"。現在バックアップは GitHub への push だけで、未コミット分は失われる
J-5AIエージェントの自動メモリ(会話をまたいで保持する記憶ファイル)をデータリポジトリへ移設する範囲⭐ memory/*.md のみ(17ファイル・68KB・推奨) / ディレクトリ全体(119MB)→112MBがgit履歴に入り消せない。しかもエラーは出ない
J-6索引に sessions/(AIエージェントの作業記録)を含めるか⭐ 既定は除外・--include で戻す(推奨)。索引なし検索実験の問い1(「アプリの収益化について過去1年間に考えたこと」)の答えは sessions/ の中の666KBのファイルにあった。単純除外では答えが消える。2026-03〜08-23の記録は sessions/ の作業記録にしか残っていない(同期間の会話ログは消失済み)
Q-6正本の更新イベントをどこに書くか
🔴 設計検討に当たった2エージェントの結論が一致した、唯一の「本人の裁定が要る」事項
案A: いまの作業記録の ## Updated Files 節のまま・ノイズを我慢 / 案B: docs/changelog/YYYY-MM-DD.md に1行足す・正確さを取る。現況実測: 正本137本中72本(52.6%)が無記録
9-1月次記録7ファイル(finance/cashflow/2026-08.md 等)の行き先A: 締め日の記録にする / B: 日付なしの正本にする。N-3(月粒度廃止)を適用すると必ずどちらかに倒す必要がある
J-=実装計画(mvp-implementation-plan.md)、Q-6=日付検索設計セッション、9-1=note-format-spec.md §9 の番号。

文書と現物のズレ(実測で確認)

✅ 実測済 2026-09-04 14:39〜15:05 に索引と実ファイルを直接確認した結果、仕様と実装が一致していない箇所が4つある。

仕様現物
N-3 / N-4(YYYY-MM 廃止・見出し段廃止)。文書は「確定仕様」と書いている🔴 未実施。索引の doc_date_source 分布は path_day: 7,332 / NULL: 2,642 / path_month: 95 / heading: 4。コード側にも両方の段がまだ残っている
docs/changelog/(正本の更新イベントの置き場所)⬜ 未作成。Q-6 が未決なので整合的
.claude/skills/search/SKILL.md(手順13)⬜ 未作成。対話中の AI が会話の中で過去の記録を引く口がまだ無い
launchd ジョブ3本✅ 完全一致で稼働中。数少ない「文書=現物」の箇所
🔴 いま体重の実測値がどこにも記録されていない

旧形式の health/weight.md は 2026-09-04 で凍結され(最終行 2026-08-28)、新形式の health/metrics/ はパーサ未実装で全項目「未取得」のプレースホルダのまま。移行期間中、体重の新規記録が落ちる先が無い空白が発生している。

⭐ 日付検索については、片方はもう答えが出ている

「日付単位で試行過程を引きたい」(A)は実装ゼロで今日から使える: search "<語>" --include sessions --since D --until D。「sessions を既定除外から外す」案は不採用 — 出力上限(既定 24,000 バイト)に達するまでの余裕が約35ファイル=3〜6日分しかなく、恒久設計にならないため。

残っているのは(B)正本の更新イベントの置き場所(Q-6)だけで、こちらは技術的には決着していて運用の裁定を待っている状態。追加実装は検知器1本(bin/check-change-log.sh)のみの予定。