備忘録/にわかエンジニアが好きなように書く

個人的にとりあえず仕組みを知るためにとりあえず動くまで構築や動作をみただけの単なる操作ログです。個人用の備忘録となり、最新の導入手順は個別に確認してください。 ※変な内容や間違いを書いているなどありましたらコメントやご指摘いただけると幸いです。

自律AI運用週報 2026/7/27-8/1: ブログ生成の空本文バグ根治と反証検証付き監査への一括対応

今週の状況

指標 今週 (7/27-8/1) 備考
発見・修正した構造バグ 4 件 (空本文 / 後埋め飢餓 / 偽シグナル / 無音故障) すべて根本原因まで特定
反証検証付き監査の所見対応 13 所見に一括対応 13 エージェント監査、CONFIRMED 7/8
後埋め一括処理 4,258 件 measured (errors 0) t5 366→4,076 / t20 0→2,306
実弾取引 0 件 (停止継続、意図的) 解除条件の観測データ蓄積中
回帰テスト 2690 passed 退行なし

今週の一言: ブログ生成パイプラインが 2 週連続で空本文になっていた根本原因を特定し、共有モジュールの fallback 誤認識を防御層で遮断した週。同時に 13 エージェントによる反証検証付き監査の所見へ一括対応し、数ヶ月間の無音故障 (速報検知の沈黙) を根治した。


今週やった実装と改善

改修#01: ブログ生成の空本文バグ — 2 段の不具合が重なった根因修正

7/18 と 7/25 に週次記事の本文が 0 字で出力されていた。調査の結果、2 つの不具合が連鎖していた。

問題 1: トークン上限不足による JSON 切断

従来の max_tokens=4096 では、日本語で 3500-5000 字の記事 (≒5000-7500 token) を出力しきれず、JSON 本体が途中で切れて parse 失敗に陥っていた。

問題 2: 共有モジュールの fallback が誤認識を招く

共有の JSON 抽出関数は全 parse 失敗時に、取引ドメイン専用のキーワード抽出 fallback へ逃げる。この fallback は本文中の「BUY」「HOLD」といった単語を拾って {"position": "HOLD"} を返すため、呼び側の isinstance(result, dict) が True になり「成功」と誤認された。

実証 (実際の再現テスト):

# 入力: ブログ本文の断片 "...討論が BUY と結論しても最終 HOLD に..."
result = BaseAgent._extract_json(blog_text)
# 出力: {'position': 'BUY'}
# → title/body_md キーがないため parse 失敗のはずが、
#   isinstance(result, dict) == True で成功と判定されていた

対処: ブログ層で防御を追加

共有モジュールは取引側でも使われているため変更せず、ブログ生成層で防御した。

  • 出力トークン上限を既定 16000 に引き上げ (env で上書き可)
  • ブログの期待キー (title / body_md) を持たない dict は parse 失敗として扱い、取引 fallback の戻り値を弾く判定を追加
  • 生成・修正の両方の呼び出しに適用

実 API 検証: 劣化なし・本文 7,146 字で正常生成を確認。

改修#02: findings (深掘り素材) の手動作成を自動化

深掘り素材を毎週人手で書いていたため、素材がない週は LLM が自由作文する事故につながっていた。commit 本文には実測データ・根本原因・対処が詳細に書かれており素材として十分な密度があるため、そこから自動合成する経路を実装した。価値スコア (根本原因/実測/乖離/根治 等のキーワード + 本文量) で上位 6 件を「発見」として構造化する。人手版があればそちらを優先する。

実測: 今週分を 9,157 字で自動合成 (対象 commit から深掘り 5 件)。

改修#03: タイトル形式違反の自動修復

LLM が承認形式 (「自律AI運用週報 YYYY/M/D-M/D: サブタイトル」) を外すことがあった。実例: 「自律AI運用週報 2026-07-20~2026-07-25」(区切り文字違い + サブタイトル無し) で公開が止まっていた。日付部分は当週から決定論で組み立て、サブタイトルは LLM 出力から救出する修復関数を実装し、修復できれば公開を止めない方式にした。

改修#04: 後埋め飢餓の根治 — 古い記録を優先する設計に是正

7/24 の修正が本番で効いていなかった。5 営業日後・20 営業日後リターンの後埋めが 4 日間 1 件も進まず、t5=366 固定、t20=0 のまま停滞していた。

根本原因: 選択順序の見落とし

選択条件は 7/24 に直したが、「新しい順に 200 件」という並び順と件数制限を見落としていた。夜間バッチが選ぶ 200 件が全て「3 日前の 1 日後リターン待ち」で埋まり、後埋め待ち 4,686 件 (8 日以上前) は新しい順の下位で永久に到達しない飢餓状態だった。さらに保持期限の超過分は選択対象外のため、放置すると恒久喪失するところだった。

対処:

  • 新規測定 (t1) と後埋め (t5/t20) を別クエリ・別予算に分離
  • 後埋めは古い順 — 失効間近を優先
  • 保持期限を 45→55 日に延長、後埋め予算 (既定 600 件) を設定可能に
  • 開示日が価格履歴の窓外なら測定しない整合性ガードを追加

実地検証: 一括追いつきで t5 366→4,076 / t20 0→2,306 (4,258 measured, errors 0)。これで初めて 5 営業日後ホライズンの分析が可能になった。

改修#05: 無音故障の根治 — 速報検知が数ヶ月沈黙していた

7/28 監査で、ニュース速報・新規開示の検知イベントが数ヶ月間一度も発火していなかったことが判明。市場監視モジュールに三重の誤りがあった。

  1. 存在しないパッケージ名からの import (常に ImportError)
  2. 収集クラス名の綴り誤り (大文字小文字の不一致)
  3. 正規化された data 形状の unwrap 漏れ

例外は debug レベルで握り潰されていたため、誰も気づかないまま沈黙していた。import パスとクラス名を修正し、握り潰しを warning に格上げして今後の無音死を防止した。


AI の判断とセーフティネットの動き

LLM の推論例 (実際の討論ログから)

信頼度 0.48 で BUY を出さなかった判断の verbatim (銘柄名は匿名化):

ある銘柄のテクニカルシグナルは強気(RSI 60.1、MACD ゴールデンクロス、SMA5/25 上抜け、20 日高値圏)で短期モメンタムは確認されるが、以下の理由により買い判断は保留:

  1. 直結カタリスト極限定: 新規材料・決算発表・業績改善要因が不在。テクニカルのみに依存した買いは根拠不足
  2. 既織り込みリスク: 20 日高値圏での買いは既に市場が材料を織り込んでいる可能性が高い
  3. 市場レジーム『range』による上値制約: マクロ環境が方向感を欠く中での上値余地は限定的
  4. 売却タイミング判断の曖昧性: カタリスト不在での買いは、反発時の利確・損切りの根拠が不明確
  5. 信頼度 0.48 は買い閾値(0.5)未満

テクニカル指標だけが強くても、カタリスト不在と確信度不足を理由に自ら見送る判断が機能している。

セーフティネットの動き

事象 挙動 評価
実弾停止 (継続中) 新規 BUY をゼロに維持 意図的。解除条件のデータ蓄積中
確信度ゲート 信頼度 0.48 の BUY 判断を閾値 0.5 未満として保留 上記 verbatim の実例
ブログ公開の構造ゲート 8/1 の自動生成で節順序違反を検出し公開をブロック 実際に発火 (後述)
監査での無音故障検出 数ヶ月沈黙していた速報検知を発見 「発火ゼロ = 正常」ではなかった実例

守られた事例: 8/1 (土) 22:00 の週次ブログ自動生成で、LLM が「AI の判断」より前に配置する節順序違反を起こした。構造ゲートが公開直前にこれを検出し、投稿をブロックした。中身の品質 (本文 12,139 字・題材は実データ準拠) は高かったが、承認された構成に一致しないものは公開させない設計が機能した。

破られそうになった事例: 速報検知の無音故障は「エラーが出ていない = 動いている」という思い込みの死角だった。例外が debug レベルで握り潰されていたため、監視上は正常に見え続けた。発火件数ゼロが数ヶ月続くこと自体を異常として検知する仕組みが必要だった。


ブログ生成パイプラインの空本文バグ

事象の要旨

週次ブログ記事が 2 週連続 (7/18, 7/25) で本文 0 字で出力されていた。1 度目は業界一般論の記事が自動公開されてしまい、2 度目は防御ガードで下書き停止した。だが「なぜ空になるのか」は未解明のまま残っていた。今週その根本原因を特定した。2 つの不具合の連鎖だった。

実際に観測された状態

7/25 の自動生成ログ (実記録):

pipeline: draft generated len=0
BlogDraftAgent: body_md too short (0 < 500) — marking degraded
pipeline: draft is DEGRADED (empty/stub origin) — 公開予約は行わず下書き止まりにします

生成は「成功」として返ってきているのに本文が 0 字。LLM が空を返したのではなく、返ってきた出力の解釈段階で中身が捨てられていた

根本原因

原因 1: トークン上限の不足

max_tokens=4096 では日本語の 3500-5000 字記事 (≒5000-7500 token) を出力しきれず、JSON 本体が途中で切れて parse 失敗に陥った。

原因 2: 共有 fallback の誤認識

共有の JSON 抽出関数は、全 parse 失敗時に取引ドメイン専用のキーワード抽出 fallback へ逃げる設計だった。挙動を単純化すると次のイメージになる (実装の簡略図であり、実コードの転記ではない):

# 概念コード (簡略化イメージ)
def _extract_json(text):
    try:
        return json.loads(text)        # 切断された JSON → 失敗
    except Exception:
        return _extract_kv_fallback(text)  # 取引用 fallback へ

def _extract_kv_fallback(text):
    # 本文中から取引キーワードを探す (本来は取引判断の解析用)
    if "BUY" in text: return {"position": "BUY"}
    if "HOLD" in text: return {"position": "HOLD"}

ブログの本文には「討論が BUY と結論しても最終 HOLD に潰される」のような記述が普通に含まれる。そのため fallback が {"position": "HOLD"} のような dict を返し、呼び側の isinstance(result, dict) チェックが True になって「成功」と判定されていた。title も body_md も無い dict から空文字が取り出され、本文 0 字の記事が「正常生成」として保存された。

事象の連鎖

TD

対処と残課題

項目 内容 状態
トークン上限 既定 16000 に引き上げ 実施済
期待キー判定 title/body_md を持たない dict を parse 失敗扱いに 実施済
実 API 検証 劣化なし・7,146 字の正常生成を確認 確認済
共有 fallback の設計 取引専用 fallback が他ドメインで誤発火し得る構造は残存 持ち越し

教訓: ドメイン固有の fallback を共有ユーティリティに置くと、別ドメインの呼び出しで「失敗が成功に化ける」。fallback は呼び出し側が明示的に選ぶ設計にすべきだった。


副次発見: 後埋め飢餓と偽シグナルの根治

後埋め飢餓の実測値

7/28 の再監査で、7/24 の修正が本番で効いていなかったことが判明した (実測値、監査記録より):

  • 後埋め待ち: 4,686 件 (8 日以上前の開示)
  • 5 営業日後リターンの測定済み: 366 件のまま 4 日間変化なし
  • 20 営業日後リターンの測定済み: 0 件

選択条件は直したのに、「新しい順に 200 件」という並びと件数制限が古い待機分を永久に選ばせなかった。修正の検証を「コードが直ったか」で終え、「本番のデータが実際に進んだか」まで見ていなかった。同型の見落とし (測っているつもりで測れていない) はこれで 3 週連続になる。

特徴量分析の偽シグナル

出来高比が SIGNAL (t=2.19) に見えたが、成分分解すると正体はギャップだった (実測):

生 t1_close  IC=+0.007 t=+0.25
ギャップのみ IC=+0.078 t=+2.70  ← シグナルの正体
執行可能     IC=+0.032 t=+1.12  → NO_SIGNAL

適時開示の 86% は引け後開示で、ギャップは寄付時点で価格に織り込まれており取得不可能。執行基準 (起点=翌寄り) に是正し、「ギャップとだけ相関する合成データで SIGNAL を出さない」再発防止テストを追加した。

是正後に残った本物の所見

執行基準で測り直した結果 (実測):

gap_pct × t1  IC=-0.069 t=-2.735 / t5 IC=-0.073 t=-2.947 (Bonferroni 生存)
ビン別: 高ギャップ群の日次超過 -0.355% t=-2.72 が唯一の有意
低ギャップ群は正だが非有意・往復コスト未満

結論: 「高ギャップは避ける」は有意、「低ギャップで儲かる」は非支持。既存の選定ガードと方向・閾値が整合しており、新規アルファではなく既存ガードの統計的裏付けが得られた。エッジを探して見つからなかったことも、そのまま記録する。


自己改善サイクルの回転記録

LR

今週のサイクル動作

項目 結果 備考
反証検証付き監査 13 エージェントで実施、CONFIRMED 7/8 所見 13 件に一括対応
対応の分離 protected 領域 (監査系 4 ファイル等) は人間対応として分離 AI は不可侵領域を触らない
完走 (自動 merge) 0 回 監査対応は人間承認経由
抽出された勝ちパターン 今週 0 個 監査対応が主体

代表トレース: 後埋め飢餓の修復

  1. 再監査 (7/28): 「5 営業日後の測定数が 366 のまま 4 日間変わらない」→ 異常として特定
  2. 原因分析: 「後埋め待ち 4,686 件が新しい順の選択で永久に到達しない」→ 飢餓構造と判明
  3. リスク評価: 保持期限超過で恒久喪失の危険 → 優先対応
  4. 実装: 新規測定と後埋めを分離、後埋めは古い順・別予算に
  5. 効果測定: t5 366→4,076、t20 0→2,306、errors 0 → 根治を実データで確認

意味: 「修正した」ではなく「本番データが実際に進んだ」まで検証して初めて完了とする、という規律が 3 週連続の同型見落としから定着しつつある。


持ち越しの課題と、次に期待できる変化

前週から持ち越している問題: - 実弾停止の解除条件 (20 営業日分の観測データ) の蓄積継続 - 共有 fallback の設計問題 — ブログ層の防御は対症療法で、他の生成経路でも同じ誤認識が起き得る構造は残存 - protected 領域 (監査系ファイル・閾値是正) の人間対応 - 8/1 の節順序違反 — ゲートは守ったが、順序を機械的に修復して公開まで通す仕組みは未実装だった (今週末に対応)

今週システムが自律的に学習したこと: - 「失敗が成功に化ける」経路の存在: ドメイン固有の fallback が共有ユーティリティに置かれると、別ドメインの呼び出しで parse 失敗が dict として返り、成功と誤認される - 「発火ゼロ = 正常」ではない: 速報検知は数ヶ月間エラーも出さずに沈黙していた。ゼロ件が続くこと自体を異常として扱う監視が必要 - 修正の完了条件は「本番データが進んだか」: コードが直ったかではなく、実データの変化で検証する (3 週連続の同型見落としからの教訓)

次週に期待できる変化・観測対象: - 5 営業日後ホライズンの分析が初めて可能に (後埋め 4,258 件の追いつき完了により) - ブログ生成の完全自動化が節順序修復込みで安定するか - 速報検知の復旧後、実際にイベントが発火し始めるか

未解決の仮説: - 他の生成パイプラインでも共有 fallback の誤認識が起きているか (全呼び出し箇所の監査が必要) - 「新しい順 + 件数制限」の飢餓は他の夜間バッチにも潜在しているか

参考

本記事で扱った概念の一次情報・解説:


※本記事は自律 AI 運用システムの振り返りログを元に AI 補助で作成しています。