# Rei-Automator Phase 0/1 実装スペック — chat-Claude 回答

**宛先:** ローカル側エージェント（rei-aios / rei-automator-mcp 作業担当）
**日付:** 2026-08-16
**入力:** inventory 抽出結果の 5層マッピング + 6 open questions
**前提:** handoff markdown 本体は未受領。以下は共有された 5層 finding 表と 6 questions に対する回答。

---

## 0. inventory を受けての全体判断

送られてきた 5層マッピングを見て、当初の想定より状況は**良い**です。理由は3つ。

1. **実行層がすでに厚い**（系統A 14 + 系統B 12 kinds）。実行層は本来いちばん物量が要る層で、ここが埋まっているのは大きい。
2. **安全層に土台がある**（3-stage lifecycle / whitelist / DANGEROUS_PATTERNS / dryRun default）。ゼロから作るのではなく、足りない口を塞ぐ作業。
3. **判断層のギャップが正確に特定されている**。これが分かっている時点で Phase 1 の作業範囲は確定したも同然。

したがって Phase 1 は「全面書き換え」ではなく **判断層に interface を1枚差し込み、安全層に3つ足す** という限定作業になります。想定より軽い。

ただし順序だけは譲れません。**判断層 interface → 安全層 → その他**。判断層を後回しにすると、以降に書くコードが全部 provider 依存になり、後から剥がすコストが跳ね上がります。

---

## 1. 6 open questions への回答

### Q1. ReiAutomatorBridge dormant → 削除 / 復活 / retain-as-archive

**→ retain-as-archive。ただし依存グラフからは完全に外す。**

- `archive/` 配下に移動し、ビルド対象と import 経路から除外する
- 削除しない理由：dormant code は「なぜそう作ったか」の記録として価値がある。復活判断は Phase 1 でカーネル境界が確定してからのほうが正確に下せる
- 復活させない理由：境界が決まる前に直すと、境界確定後にもう一度直すことになる（二重投資）
- **再訪タイミング：Phase 1 完了時**。そのとき「新カーネル上で書き直したほうが速い」なら、archive のまま置いて新規実装に進む

判断基準の一般則：*動かない コードは、依存グラフに残っている限り必ず腐る。しかし読める場所にある限り腐らない。*

### Q2. rei-automator-daily.ts の位置 → 実行層 tool or 判断層 lens

**→ 実行層 tool。**

「daily」は**時間トリガー**であって判断ではありません。判断層に置くと「いつ動くか」と「何をするか」が結合し、片方だけ差し替えられなくなります。

一般則として、層の振り分けはこう決めます：

| 判定 | 層 |
|---|---|
| モデルに問い合わせる／方針を選ぶ | 判断層 |
| 時間・イベントが引き金 | 実行層（またはカーネル外周のスケジューラ） |
| 外界の状態を読む | 認識層 |
| 外界の状態を変える | 実行層 |

**将来の扱い：** Phase 3 の常駐化で、これはスケジューラプラグインに昇格させるのが自然な進化です。今は実行層 tool に置き、そこから判断層を*呼び出す*形にしておけば、昇格時の書き換えが最小で済みます。

### Q3. plugin API 境界 → hypervisor / EventBus / RuntimeBus / MCP のどこ

**→ 二層に分ける。対外は MCP、対内は EventBus。hypervisor と RuntimeBus は公開しない。**

| 境界 | 用途 | 公開範囲 |
|---|---|---|
| **MCP** | 他人が書くプラグイン | 公開 API |
| **EventBus** | 自分が書く内部モジュール | 準公開（バージョン付き） |
| hypervisor / RuntimeBus | 内部実装 | **非公開** |

理由：

- **MCP を選ぶ理由** — すでに業界標準として定着しており、他人がプラグインを書く際の学習コストがゼロに近い。エコシステム（Phase 4）を本気で狙うならここ以外にない
- **hypervisor / RuntimeBus を公開しない理由** — 公開 API にした瞬間、内部実装を変えられなくなります。全部入りを目指すなら内部は何度も作り直すことになるので、ここを固定するのは自殺行為
- **副次的だが重要な効果** — MCP は別プロセス通信なので、ライセンス上「プラグインは派生著作物ではない」と主張しやすくなります（Q5 と直結）

### Q4. 判断層 provider scope → 4 minimum vs 全域

**→ 4 minimum（Claude / GPT / Gemini / local）。ただし数より interface の完全性が本質。**

抽象化層の設計には経験則があります：**2つで足りているように見えて、3つ目で破綻し、4つ目で境界が正しくなる。** 4実装すれば interface はほぼ確実に正しくなります。全域対応は Phase 2 以降にプラグインとして。

重要なのは provider の数ではなく、**以下の差異を漏らさず吸収できているか**です：

- tool calling のスキーマ形式と、並列 tool call の可否
- streaming イベントの粒度
- vision（画像入力）の有無とエンコード形式
- context 長と max output tokens
- structured output（JSON schema 強制）のネイティブ対応可否
- reasoning / thinking トークンの扱い

`local` は Ollama 互換エンドポイントを1本通せば十分です。ローカルモデルは tool calling が弱いことが多く、**そこで露出する制約が interface の正しさを検証してくれる**ので、4つ目として最も価値があります。

### Q5. License 統一 → 系統B AGPL 予告 vs 系統A AGPL+Commercial dual

**→ AGPL + Commercial dual に統一。加えて「プラグインは派生著作物ではない」を明文で宣言する。**

理由：

- AGPL 単体だと、プラグイン作者が「自分のプラグインも AGPL にしないといけないのか」を恐れて参入しません。エコシステム（Phase 4）を殺します
- dual なら「本体は AGPL、プラグインは作者の自由」と言えます
- Linux kernel の syscall exception に相当する例外条項を明記してください。**MCP 境界（別プロセス通信）を採用していることが、この主張の技術的裏付けになります**（Q3 の答えと噛み合う）

**タイミング：Phase 2 のプラグイン API 公開より前に確定。** 公開後の変更は既存プラグイン作者との交渉が必要になり、実質不可能になります。

> 私は弁護士ではないので、これは法的助言ではありません。商用ライセンスで収益化する計画があるなら、条項の文面は専門家の確認を取ってください。

### Q6. 配布 scope → 系統B public GitHub vs 系統A 個人使用

**→ 現状維持（系統B public / 系統A 個人使用）。Phase 2 まで広げない。**

前回「全ユーザー対象」という話をしましたが、それは**最終的な到達点**であって、いま公開範囲を広げる理由にはなりません。

Phase 0〜1 はカーネル境界のリファクタ期間です。この期間に系統A を公開すると：

- 破壊的変更を外部ユーザーに晒すことになる
- 「まだ固まっていない API」に依存する人が出て、後から動かせなくなる

**広げるタイミング：Phase 2 のプラグイン API 公開と同時。** そこで初めて「外から触っていい面」が定義されるので、公開の意味が出ます。

---

## 2. 最重要 gap：判断層 モデル抽象化 interface

### 設計方針

**superset + capability flags** 方式を採ります。すべての provider の機能の和集合を型で表現し、各 provider が「自分に何ができるか」を宣言する形です。

最小公倍数方式（全 provider の共通機能だけ使う）は採りません。vision も tool calling も使えなくなり、Rei-Automator の中核が死にます。

### interface 定義（TypeScript / 系統A 想定）

```ts
// ---- capability 宣言 ----
export interface ModelCapabilities {
  toolCalling: boolean;
  parallelToolCalls: boolean;
  vision: boolean;
  structuredOutput: boolean;      // JSON schema のネイティブ強制
  streaming: boolean;
  maxContextTokens: number;
  maxOutputTokens: number;
}

// ---- 正規化されたコンテンツ ----
export type ContentBlock =
  | { type: 'text';        text: string }
  | { type: 'image';       mimeType: string; data: string }   // base64
  | { type: 'tool_use';    id: string; name: string; input: unknown }
  | { type: 'tool_result'; toolUseId: string; content: string; isError?: boolean };

export interface Message {
  role: 'user' | 'assistant';
  content: ContentBlock[];
}

export interface ToolSpec {
  name: string;
  description: string;
  inputSchema: object;            // JSON Schema
}

// ---- 要求 / 応答 ----
export interface ReasoningRequest {
  system?: string;
  messages: Message[];
  tools?: ToolSpec[];
  maxOutputTokens?: number;
  temperature?: number;
  responseSchema?: object;
  signal: AbortSignal;            // ★必須。kill switch の実効性がここに依存
}

export interface ReasoningResponse {
  content: ContentBlock[];
  stopReason: 'end' | 'tool_use' | 'max_tokens' | 'aborted' | 'refusal';
  usage: { inputTokens: number; outputTokens: number };
  raw?: unknown;                  // 脱出ハッチ。カーネルは絶対に読まない
}

export interface ReasoningProvider {
  readonly id: string;            // 'anthropic' | 'openai' | 'google' | 'local'
  readonly capabilities: ModelCapabilities;
  complete(req: ReasoningRequest): Promise<ReasoningResponse>;
  stream?(req: ReasoningRequest): AsyncIterable<StreamEvent>;
  countTokens?(req: ReasoningRequest): Promise<number>;
}
```

### 破ってはいけない3つの規則

1. **カーネルは `raw` を絶対に読まない。** 読んだ瞬間に抽象化は死にます。`raw` はデバッグとログ専用。lint ルールで機械的に禁止するのが確実です
2. **capability が `false` の機能を暗黙にフォールバックしない。** カーネル側で明示的にエミュレートするか、明示的にエラーを投げるかの二択。暗黙フォールバックは provider を変えたときに挙動が変わり、再現不能なバグの温床になります
3. **`signal` は必須引数にする。** optional にすると必ず渡し忘れが出て、そこだけ止まらなくなります

### Phase 1 完了条件（これが満たされたら次へ進んでよい）

> **同一タスクを 4 provider で実行し、`raw` を除いた出力の「形」が完全に一致すること。**

具体的には、スクリーンショットを1枚渡して tool を1つ呼ばせるタスクを用意し、4 provider で `content` のブロック構成・`stopReason`・`usage` の型が揃うことを自動テストで確認します。これが通れば interface は漏れていません。通らない箇所が、そのまま抽象化の穴です。

---

## 3. 安全層：Phase 0 in-scope の3件

### 3-1. kill switch（未実装 / 最優先）

**3段階に分けてください。**

| 段階 | 動作 | 用途 |
|---|---|---|
| `pause` | 現ステップ完了後に停止 | 通常の中断。状態が綺麗に残る |
| `abort` | 進行中の操作を `AbortSignal` で中断 | 誤動作に気づいたとき |
| `halt` | プロセス即時終了 + 未完了操作を記録 | 暴走時の最終手段 |

実装の要点：

- カーネル root に単一の `AbortController` を置き、**全 provider 呼び出しと全実行層操作に signal を伝播**する
- 外部トリガーを最低3経路用意する — ファイル watch（`.rei-halt` の出現）、シグナル（SIGINT / SIGTERM）、IPC。**UI が固まっていても止められること**が要件です
- **`abort` 不可能な区間**を明示する。ファイル削除・メール送信・購入・決済など、開始したら取り消せない操作です。この区間に入る**直前**に signal をチェックする checkpoint を必ず置いてください

既存の `DANGEROUS_PATTERNS` と Theory#196 の判定ロジックは、そのまま「abort 不可区間の入口マーカー」として再利用できます。新規に危険操作リストを作る必要はありません。

### 3-2. dryRun 明示 API

現在 dryRun が default である点は良い設計です。足すべきは**明示性**：

- 呼び出し側が `{ dryRun: true | false }` を**必ず明示**する（default 依存を禁止）
- dryRun 実行時は「実際には何が起きたはずか」を構造化して返す（単なるスキップにしない）
- dryRun と実行の**経路を分岐させない**。同じコードパスを通り、最終の副作用発生点だけで分岐する。分岐が上流にあると「dryRun では通るのに本番で落ちる」が発生します

### 3-3. audit log 独立 store

**`execution-log.json` と同居させないでください。** 性質が違います。

| | execution-log | audit log |
|---|---|---|
| 目的 | デバッグ | 説明責任 |
| 消去 | してよい | してはいけない |
| 形式 | 任意 | 追記専用 |

実装：**append-only JSONL + hash chain**。各行に前行のハッシュを含めることで、後からの改竄が検出可能になります。

```jsonl
{"ts":"...","actor":"...","action":"file_delete","target":"...","approved_by":"...","prev":"sha256:..."}
```

これは今は過剰に見えますが、Phase 4 で法人向けに出すときに**後から遡って作れない**種類のデータです。今から溜め始める価値があります。

---

## 4. 残り3層への指示（Phase 1 では着手しない）

| 層 | 指示 |
|---|---|
| 認識層 | DOM / event stream の統一正規化は **Phase 2**。判断層 interface が確定してから、その `ContentBlock` に合わせる形で設計すると手戻りがない |
| 実行層 | 現状で最も厚いので Phase 1 では**触らない**。macro record/replay と plugin sandbox は Phase 2 |
| 記憶層 | 長期メモリは **Phase 3**。ただし Phase 1 のうちに「保存先ディレクトリ構造」だけ決めておくと後が楽（markdown 保持は OpenClaw パターンを踏襲して良い） |

---

## 5. 次の 1 週間の作業順

1. `ReiAutomatorBridge` を `archive/` へ移動、ビルド対象から除外（Q1）
2. LLM 呼び出し箇所を全て洗い出し、リスト化
3. 上記 interface を型定義ファイルとして先に置く（実装なし、型だけ）
4. 既存の呼び出しを1つだけ interface 経由に置き換え、動作確認
5. kill switch の `AbortController` をカーネル root に設置し、上記1箇所に signal を通す
6. 「abort 不可区間」を `DANGEROUS_PATTERNS` から抽出してリスト化
7. ライセンス方針を1ページで文書化（Q5）

**3 と 4 の順序が重要です。** 型を全部置いてから実装を1つ通すことで、interface の穴が最速で露出します。

---

## 6. chat-Claude 側の未受領事項

`C:\Users\user\Downloads\rei-automator-inventory-for-chat-claude.md`（15.2 KB / 200行 / 9 section）は**未受領**です。この session からはユーザーのローカルファイルシステムに到達できないため、chat 側に直接添付いただければ、以下が可能になります：

- 実ファイル単位・実関数単位での 5層 仕分け
- 既存 LLM 呼び出し箇所の具体的な置換パッチ
- 系統A / 系統B のどちらにカーネルを置くかの判断（現状は情報不足）

上記スペックは添付なしでも着手可能な範囲に限定して書いています。
