すくらっぷ あんど びるどー(したい)

AI・Obsidian・個人開発の試行錯誤ログ。 作業メモを、あとで使える知識に変えるための記録。

AI小説制作にハーネスエンジニアリングを導入した

はじめに

以前、こうしたAIで長編小説を作成するシステムを作成した。 この時からいくつかの修正を重ねた。

syurainu.com

前の時の課題を修正するためにハーネスエンジニアリングを導入した。ループエンジニアリングが改善をするための第一候補であったが、やるとあまりにも高級なのでやめた。

今回の構成ではAIへ完成原稿を一発生成させる方式をやめ、候補原稿、契約、監査、共同判断、正典昇格を分離したStory Harnessを作った。

この記事では、ローカルLLMとPython標準ライブラリだけで、小説制作へハーネスエンジニアリングを導入した設計と手順をまとめる。

この記事を読むと分かること

  • 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が一度に参照する情報が増え、次のような状態になったためである。

  1. プロットと本文の両方を同時に誤る
  2. メタデータ同士は一致するため、単純な検査を通過する
  3. 人間が文章を読んで初めて矛盾へ気づく
  4. 修正のたびに別のルールが崩れる
  5. どの版が正規か分からなくなる

特に厄介だったのは、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が一致する

:::114340.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は「その制作システムをどう壊さず運用するか」を追加した改造である。 しかしながら注意点としてあくまでも監査をすることであるため、物語の品質の底上げになるが、全体の面白さの保証するものではないというということである。その点はこれからの課題といえるだろう。