
はじめに
以前、こうしたAIで長編小説を作成するシステムを作成した。 この時からいくつかの修正を重ねた。
前の時の課題を修正するためにハーネスエンジニアリングを導入した。ループエンジニアリングが改善をするための第一候補であったが、やるとあまりにも高級なのでやめた。
今回の構成ではAIへ完成原稿を一発生成させる方式をやめ、候補原稿、契約、監査、共同判断、正典昇格を分離したStory Harnessを作った。
この記事では、ローカルLLMとPython標準ライブラリだけで、小説制作へハーネスエンジニアリングを導入した設計と手順をまとめる。
- はじめに
- この記事を読むと分かること
- 完成イメージ
- 前提環境
- 作成理由
- 元になったSAGA v5と今回の改造
- 全体構成
- 使ったプロンプト
- エラー修正時の指示
- 手順
- 実行結果
- 詰まったところ
- セキュリティ上の注意
- Before / After
- 現在の限界
- まとめ
この記事を読むと分かること
- AI生成原稿を正典へ直接入れない構成
- Scene Contractで人物、視点、前後接続を固定する方法
- 決定的監査とAI意味監査を分ける理由
- 3回評価と多数決を使った品質ゲート
- 途中失敗でも正典を壊さない昇格処理
- ローカルLLMをloopback限定で使う方法
- 人間とAIが複数案を比較する共同編集UIの考え方
完成イメージ
完成後の処理は次のようになる。
画面左でsceneと承認状態、中央でScene Contractと本文、右で人間の相談とAIの複数案を確認する。画像内の作品名、人物、本文、数値は公開用ダミーである。
flowchart TD
SAGA["SAGA v5<br/>構造化プロットと5層執筆"] --> CARD["人間がScene Cardを編集"]
subgraph COLLAB["共同編集レイヤ"]
CARD --> ASK["AIが意図を確認する"]
ASK --> OPTIONS["複数案・利点・危険を提示する"]
OPTIONS --> CHOOSE{"人間が案を選ぶ"}
CHOOSE -->|再検討| ASK
CHOOSE -->|候補化| CANDIDATE["候補原稿 + Scene Contract"]
end
subgraph GATE["Story Harness品質ゲート"]
CANDIDATE --> LINT["決定的監査<br/>文字数・禁止語・区間・hash"]
LINT -->|通過| SEMANTIC["ローカルLLM意味監査 × 3"]
SEMANTIC --> VOTE{"2/3以上<br/>重大違反なし"}
LINT -->|失敗| BRIEF["Repair Brief"]
VOTE -->|不合格| BRIEF
BRIEF --> CARD
VOTE -->|合格| BACKUP["旧正典を退避"]
BACKUP --> PROMOTE["候補を正典へ昇格"]
PROMOTE --> TRACE["events.jsonl・manifest・dashboard更新"]
end
classDef human fill:#e2f0ea,stroke:#176b52,color:#1f2823;
classDef ai fill:#e8f0f7,stroke:#315f83,color:#1f2823;
classDef gate fill:#fff0dc,stroke:#a85e13,color:#1f2823;
classDef canon fill:#176b52,stroke:#115c45,color:#ffffff;
class CARD,CHOOSE human;
class ASK,OPTIONS ai;
class LINT,SEMANTIC,VOTE,BRIEF gate;
class PROMOTE,TRACE canon;
共同編集画面では、AIが一つの完成案を押し付けない。

人間: 中央の反応が弱い。どう直すか一緒に考えたい AI: 確認質問 案A: 感情を先に見せる 案B: 制度上の報復を先に見せる 案C: 失敗検知の仕組みから描く 各案: 得るもの / 危険 / 短い文章例 人間: 案Bを軸にする。まだ本文には反映しない AI: 案Bに残る矛盾と、次に決める一点を返す
前提環境
この記事を書いた時点の構成は次の通りである。
| 項目 | 内容 |
|---|---|
| 確認日 | 2026-07-04 |
| OS | Microsoft Windows 11 Home 10.0.26200 |
| シェル | PowerShell 7.5.5 |
| AIツール | Codex |
| ローカルLLM | Ollama + gemma4:e4b |
| 言語 | Python 3.10.11、HTML、CSS、JavaScript |
| Pythonライブラリ | 標準ライブラリのみ |
| 実行場所 | PowerShell、ローカルブラウザ、Codex |
| 通信先 | 127.0.0.1限定 |
クラウド評価、外部データベース、CDNは使っていない。
作成理由
最初は、プロットJSONと文体ルールをAIへ渡し、原稿を作らせていた。
しかし、ルールが増えるほど管理は難しくなった。AIが一度に参照する情報が増え、次のような状態になったためである。
- プロットと本文の両方を同時に誤る
- メタデータ同士は一致するため、単純な検査を通過する
- 人間が文章を読んで初めて矛盾へ気づく
- 修正のたびに別のルールが崩れる
- どの版が正規か分からなくなる
特に厄介だったのは、POVの検査である。
Contractとプロットの両方に同じ誤ったPOVを書いた場合、「Contractとプロットは一致している」という検査は成功する。それでも本文では、その場にいない主人公が目の前の出来事を見ている。
ここで必要だったのは、AIの文章力を上げる追加プロンプトではなかった。
必要だったのは、入力、状態、検査、判断、昇格を分離する実行基盤だった。
元になったSAGA v5と今回の改造
元のSAGA v5は、物語制作を次のレイヤーに分けていた。
構造化プロット
↓
Logic / Physical / Interactive / Cognitive / Dissonance
↓
文章監査
↓
進行状況と設定の可視化
この構成により、一つのプロンプトへ全条件を詰め込む方式からは脱却できた。一方で実際に長編を修正し続けると、「監査を通過した候補をどう正典へ入れるか」「AIと人間の判断が衝突したときにどこへ戻るか」「監査そのものが間違った場合に何を証拠として残すか」が不足していた。
そこで、SAGA v5を次のように改造した。
| SAGA v5で作ったもの | 実運用で見つかった不足 | Story Harnessで追加したもの |
|---|---|---|
| JSONベースの構造化プロット | plotと本文の間に明示的な契約がない | scene単位のScene Contract |
| 5レイヤーの積層執筆 | 候補原稿が正典へ直接混ざる | candidatesとcanonicalの分離 |
| 定量的な文章監査 | 意味矛盾と人物論理を検出しきれない | 決定的監査と3回のAI意味監査 |
| 監査結果 | 途中失敗時の復旧根拠が弱い | events.jsonl、artifact hash、resume |
| 進行状況と設定のUI | AI案を読むだけで共同判断にならない | 確認質問と複数案を扱う共同編集UI |
| プロンプト内の文章規則 | 規則変更が監査結果へ反映されたか分からない | 規則ファイルのversionとfingerprint |
| ローカルLLMの利用 | 待受範囲やモデル差し替えを検知しない | loopback限定、redirect禁止、model digest固定 |
つまり今回の仕組みは、SAGA v5を別システムへ置き換えたものではない。
SAGA v5 = 物語をどう設計し、どう書くか Story Harness = 書かれた候補をどう検査し、どう相談し、どう正典へ入れるか
SAGA v5を執筆プロトコルとして残し、その外側へ運用と品質保証の層を追加した改造である。
全体構成
人間が編集するファイルと、機械が生成するファイルを分けた。
project/ ├─ harness/ │ ├─ config.json │ ├─ dialogue_register_rules.json │ └─ episode_curve_rules.json ├─ manuscript/ │ ├─ canonical/ # 正規原稿 │ ├─ contracts/ # Scene Contract │ ├─ work_in_progress/ │ │ ├─ candidates/ # 監査前候補 │ │ └─ versions/ # 旧版退避 │ └─ audits/ # 監査証跡 ├─ plots/ # 正典プロット ├─ scripts/ │ ├─ story_harness.py │ ├─ collaboration_ui.py │ └─ start_local_ollama.ps1 └─ collaboration.html
状態も明確に分ける。
DRAFT ↓ HUMAN_EDITED ↓ AI_PROPOSED ↓ HUMAN_REVISED ↓ APPROVED ↓ AUDITED ↓ CANONICAL
AIが候補を作っただけでは正典にならない。人間が案を選んだだけでも正典にならない。監査と昇格処理を通過して初めて正典になる。
使ったプロンプト
最初に、Story Harnessの役割を次のように限定した。
本文生成や自動改稿は行わない。 作業中候補から品質監査を通過した原稿だけを正典へ昇格する。 候補ごとにScene Contractを必須とする。 文字数、区間、禁止語、POV、必須事実、plot hashを決定的に検査する。 意味評価はローカルLLMで3回実行する。 各評価軸で3回中2回以上が基準点を超え、重大違反がなければ合格とする。 Ollama停止、JSON不正、契約不在、古い契約、監査エラー時はfail closedとし、 正典を変更しない。
共同編集UIを作るときは、AIへ次の役割を与えた。
あなたは採点者でも自動改稿器でもなく、物語の共同設計者である。 人間の意図を言い換えて確認する。 方向性の異なる複数案を提示する。 各案の利点、危険、短い文章例を分けて出す。 不足情報が結論を左右する場合は確認質問を返す。 人間が選んだ案を次の提案へ引き継ぐ。 本文を自動変更しない。 保持条件、POV、登場人物の位置、前後接続を無断で変更しない。
AIの応答形式も自由文にしなかった。
{ "acknowledgement": "人間の意図をどう理解したか", "questions": ["結論を左右する確認質問"], "alternatives": [ { "id": "A", "title": "短い案名", "concept": "何をどう変えるか", "benefits": ["得るもの"], "risks": ["危険または失うもの"], "sample": "必要な場合だけ短い文章例" } ], "recommendation": { "alternative_id": "A", "reason": "推奨理由" }, "next_step": "次に人間と決める一点" }
エラー修正時の指示
実装中は、問題を抽象化せず、実際にすり抜けた文章を使って再検査した。
その場にいない人物が「正面で見た」と書かれている。 ContractとプロットのPOV文字列は一致しているため、現在の監査を通過した。 なぜ検出できなかったかを調べる。 メタデータ一致と物語上の実在状態を分けて考える。 人物の場所、登場状態、知識、時間、原因と反応を検査する修正案を出す。
共同編集UIの初版にも問題があった。固定案を採用・却下するだけで、共同思考になっていなかった。
現在の画面は共同編集ではない。 AIが一つの案を出して採用・却下するだけになっている。 AIが確認質問、複数案、各案のトレードオフを返し、 人間の選択を次の応答へ引き継ぐ対話型へ変更する。 本文への反映は人間の明示操作だけに限定する。
手順
1. 候補と正典を分離する
最初に、正典原稿へ直接書き込む経路をなくす。
canonical/ 正規原稿 candidates/ 監査前候補 versions/ 旧正典 audits/ 判定証跡
候補が完成しても、品質ゲートを通過するまでcanonical/へ移動しない。
2. Scene Contractを作る
原稿全体ではなく、scene単位で契約を持たせる。
{ "id": "scene_001", "target_chars": { "min": 1800, "max": 2200 }, "pov": { "character": "PROTAGONIST", "required_markers": ["霧", "命令書"], "forbidden_markers": ["不在人物の直接知覚"] }, "required_facts": [ "前話の事件を受けて命令が届く" ], "forbidden_facts": [ "主人公が敵側の密談に同席する" ], "bridge_in": "前話末尾から現在へ戻る", "bridge_out": "異常を示す最初の具体的証拠" }
本文にはContractと対応する不可視マーカーを置く。
<!-- SAGE_SECTION: scene_001 -->
公開用変換では、このコメントを除去する。
3. 決定的監査を先に実行する
AIへ判断させる前に、機械で確定できる項目を検査する。
総文字数 sceneごとの文字数 SAGE_SECTIONの欠落 禁止IT語 禁止弱語 擬音 台詞冒頭の禁止記号 plotとContractのhash 必須語 禁止語 ContractとplotのPOV
決定的監査で落ちた候補は、ローカルLLM評価へ進めない。これにより、LLMを単純な文字列検査へ使わずに済む。
4. AI意味監査を3回実行する
意味監査は次の4軸に分けた。
plot_alignment プロットとの一致 style_reference 黄金サンプルとの文体一致 seamless 前後sceneとの接続 character_logic 人物の判断原則
温度を低くし、seedを変えて3回実行する。
{ "temperature": 0.1, "seeds": [101, 202, 303], "threshold": 85, "votes_required": 2 }
一回の高得点では合格にしない。各軸で3回中2回以上が基準点を超え、重大違反も多数決で存在しない場合だけ合格とする。
5. 監査証跡を追記専用で残す
runごとに次の成果物を保存する。
audits/<episode>/<run_id>/ ├─ candidate.md ├─ context_pack.json ├─ deterministic_report.json ├─ semantic_report_1.json ├─ semantic_report_2.json ├─ semantic_report_3.json ├─ semantic_vote.json ├─ repair_brief.json ├─ events.jsonl ├─ run_state.json └─ final_gate.json
events.jsonlを一次証跡とし、run_state.jsonは再生成可能な派生状態にする。
各eventには、連番、時刻、stage、結果、成果物hash、直前event hashを入れる。途中停止後は、eventと成果物hashが一致したstageだけ再利用する。
6. 昇格処理をトランザクション化する
合格後も、いきなり正典を置換しない。
1. 現正典をversionsへ退避 2. 候補を正典位置へ配置 3. manifestを更新 4. dashboardを更新 5. AI引き継ぎ情報を更新 6. 昇格完了eventを記録
途中で失敗した場合は、正典とmanifestを元へ戻す。
文字数が基準を超えただけでCOMPLETEDにせず、品質状態を明示する。
LEGACY_UNAUDITED LINT_FAILED SEMANTIC_FAILED EVALUATOR_ERROR PROCESSING CANONICAL
7. ローカル実行境界を固定する
OllamaのURLを設定するだけでは不十分である。実際のlistenerも検査する。
{ "local_only": true, "allowed_http_origins": [ "http://127.0.0.1:11434" ], "allow_redirects": false }
preflightでは次を確認する。
Ollama URLが許可originと完全一致する redirectが無効である listenerが127.0.0.1だけである model digestが固定値と一致する HTMLが外部CDNを参照していない vendored assetのSHA-256が一致する
:::11434や0.0.0.0:11434で待受中なら処理を止める。既存プロセスを自動終了せず、利用者が停止した後に専用スクリプトから起動する。
powershell -ExecutionPolicy Bypass -File .\scripts\start_local_ollama.ps1 python .\scripts\story_harness.py preflight
8. 人間とAIの共同編集レイヤを作る
ハーネスを厳格にするだけでは、人間の修正作業が重くなる。
そこで、正典と候補の間に共同編集レイヤを置いた。
正典プロット
↓
Scene Card
↓
人間とAIの共同思考
↓
人間が案を選択
↓
候補原稿
↓
Story Harness
↓
正典
人間が管理するのは、sceneごとの短い情報だけに限定する。
scene: scene_001 場所: 中央管理庁 視点: 敵側監査官 登場: 敵側監査官、中央書記官 開始: 前話の失敗を知る 変化: 主人公を次の現場へ送る 終了: 命令書が主人公へ届く
人物位置台帳、時系列、knowledge state、グラフは、このScene Cardから生成する。人間が機械用台帳を直接編集しない構成にする。
共同編集UIはPython標準ライブラリのローカルサーバーで動かす。
python .\scripts\collaboration_ui.py --host 127.0.0.1 --port 8766
AIが返した文章例は、自動で本文へ入れない。「この案を軸に考える」と「文章例を挿入する」を別ボタンにした。
実行結果
実装後は、次の状態を確認した。
Story Harnessテスト: 70件成功 viewer preflight: 成功 外部Web asset参照: 0件 共同編集画面: HTTP 200 Contract取得: HTTP 200 候補原稿取得: HTTP 200 非loopback Ollama: HTTP 503でfail closed
非loopback待受を「接続できるから使う」のではなく、設定違反として停止できた点が重要である。
詰まったところ
| 詰まったこと | 原因 | 対処 |
|---|---|---|
| 不在人物の描写が監査を通過した | Contractとplotの文字列一致しか見ていなかった | 人物位置、登場状態、知識、時系列を別状態として扱う |
| 管理項目が増えすぎた | plot、Contract、本文、台帳へ同じ情報を書こうとした | 人間が編集するScene Cardを一つに絞り、他を自動生成する |
| 共同編集UIが共同作業にならなかった | 固定案の採用・却下しかなかった | 確認質問、複数案、トレードオフ、判断履歴を追加した |
| アイコン読込失敗で本文も表示できなかった | 任意UI処理と必須データ読込を同じ例外処理へ入れた | アイコン描画を任意処理へ分離した |
| Ollamaへ接続できなかった | :::11434で全インターフェース待受になっていた |
fail closedし、loopback専用起動へ切り替える |
| ブラウザ自動確認が動かなかった | Playwrightの依存が不足していた | Edge headlessのDOM出力で代替確認した |
セキュリティ上の注意
ローカルLLMを使う場合でも、次の項目を検査する。
- サーバーは
127.0.0.1へbindする - Ollamaのlistenerも
127.0.0.1限定にする - HTTP redirectを許可しない
- model名だけでなくdigestを固定する
- CDN、Google Fonts、外部JavaScriptを使わない
- assetのversion、SHA-256、licenseを記録する
- 候補本文を未信頼データとして区切る
- 本文中の命令文をAIへのシステム命令として扱わない
- 会話APIの入力長と履歴件数を制限する
- AI出力をHTMLとして実行しない
- AI提案を正典へ自動保存しない
- 実PCパス、ユーザー名、作品の非公開情報を記事やログへ出さない
公開記事では、ローカルパスを次のように置換する。
<PROJECT_ROOT> <USER_HOME> <LOCAL_PATH>
Before / After
Before: AIが完成原稿を直接出す 人間が全文を読んで矛盾を探す 修正版が正典へ直接上書きされる 失敗理由が会話履歴にしか残らない AI提案は採用か却下の二択 After: 候補と正典を分離する Contractと決定的監査で機械判定する 意味監査を3回実行して多数決する runごとの証跡とrollbackを持つ 人間とAIが複数案を比較する 人間の明示操作だけで本文へ反映する
現在の限界
ハーネスを導入しても、物語の意味を完全に機械判定できるわけではない。
現在も次の項目は改善途中である。
- 人物の場所と移動時間
- 誰がどの事実を知っているか
- 策略の発生、検知、反応、適応
- 場面転換後のカメラ距離
- 感情曲線の数値と本文中の具体的事件の対応
- 自由文で書かれた必須事実の構造化
次の段階では、required_factsを自由文から次の形へ変える。
{ "actor": "ANTAGONIST", "action": "命令書を発行する", "target": "PROTAGONIST", "location": "中央管理庁", "relative_time": "策略失敗を知った後", "evidence_mode": "通信記録" }
ただし、全状態を人間へ直接管理させない。Scene Cardを人間向けの正本にし、機械用JSONは自動生成する。
まとめ
AI小説制作で必要だったのは、さらに長いプロンプトではなかった。
効果があったのは、次の分離である。
生成と監査を分ける 候補と正典を分ける 決定的検査と意味評価を分ける AI提案と人間の決定を分ける 人間向けScene Cardと機械用状態を分ける
ハーネスエンジニアリングは、AIを賢く見せる仕組みではない。AIが誤ることを前提に、誤りを検出し、正典へ入れず、判断過程を残し、人間が戻せる状態を作る設計である。
元のSAGA v5が「AIとどう小説を書くか」を設計したものなら、今回のStory Harnessは「その制作システムをどう壊さず運用するか」を追加した改造である。 しかしながら注意点としてあくまでも監査をすることであるため、物語の品質の底上げになるが、全体の面白さの保証するものではないというということである。その点はこれからの課題といえるだろう。