AIへ渡す締切の訂正台帳:18テストで古い上書きと取消を検証

締切を9月10日から12日へ訂正したあと、古い「9月10日」というメモや、未確定の「9月14日はどうか」という提案が届いたら、次の作業へ何を渡すべきでしょうか。最後に届いた文だけを採用すると、訂正済みの値を戻したり、提案を決定として扱ったりする余地があります。
そこで、入力を消さずに残しながら、確定値だけを取り出す小さな台帳をPythonとSQLiteで作りました。人工データのデモと18テストを2026年9月29日に再実行し、古い版の入力、提案、取消、重複入力を分けられることを確認しました。
結論は、値に版番号と根拠IDを添え、更新時に「読んだ版」と「現在の版」を照合すれば、今回の有限な条件では訂正後の古い入力を stale として残せる、です。ただし、確認したのは記録プログラムの挙動だけです。LLMの回答精度、根拠の正しさ、本人認証、改ざん不能性、実際の予定表との同期は検証も実装もしていません。
公式の設計説明と、今回の実測を混ぜない
着想は、NVIDIAのNemoClaw解説にある、背景知識をMarkdown、判断や訂正の履歴をSQLiteへ分け、元の証拠を派生知識で置き換えない設計です。本稿はNemoClawの導入手順でも、公式コードの移植でもありません。同社が示すモデル評価は追試せず、当サイトで新しく作った記録部分だけを扱います。
台帳はPython公式のsqlite3資料を参照し、標準ライブラリだけで実装しました。デモはモデル、メール、予定表、外部APIへ接続しません。すべて架空の原稿名、日付、根拠IDです。
| 区分 | 実行日 | 環境 | 確認したこと |
|---|---|---|---|
| 初回検証 | 2026-09-05 | Windows、Python 3.14.2、SQLite 3.50.4 | 実装、失敗修正、18テスト |
| 公開前の追試 | 2026-09-29 | 同上 | デモ再実行、元フォルダの18テスト |
| 配布物の追試 | 2026-09-29 | 新しい一時フォルダ | ZIP展開後のデモと18テスト |
初回検証と公開前追試は別の実行です。9月29日に9月5日の失敗を再現したとは書かず、当時の記録と現在の成功を分けます。追加パッケージと実行費用は0円でした。
同じ締切へ4件届いた結果
最初に、架空の「原稿Aの締切」へ4件を順に入力しました。最初の入力は版0、訂正は版1を読んだ更新です。3件目は同じ版1を前提にしているため、訂正後の版2とは一致しません。
| 到着順と入力 | 台帳の結果 | 確定値 |
|---|---|---|
| 1:9月10日、期待版0 | applied |
9月10日、版1 |
| 2:9月12日へ訂正、期待版1 | applied |
9月12日、版2 |
| 3:古い9月10日、期待版1 | stale |
9月12日、版2を維持 |
| 4:9月14日の提案 | pending |
9月12日、版2を維持 |
4件後の確定値は次のとおりでした。
{
"event_id": "correction-2",
"kind": "set",
"value": "2026-09-12",
"evidence": "sample:correction-2",
"revision": 2
}
同じ4件から、最後に届いた値だけを選ぶ意図的に単純なルールは9月14日を返します。一方、台帳は適用済みの9月12日を返しました。これは選択ルールへの反例であり、MarkdownとSQLiteの性能比較でも、LLMが9月14日と回答した実測でもありません。文章形式でも版と承認状態を同じように管理すれば、別の結果になり得ます。
4ファイルの教材をそのまま追試する
台帳、人工デモ、18テスト、READMEをまとめたZIPを新しいフォルダへ展開し、そのフォルダで実行します。Python 3.14.2以外や別OSは未検証です。
python .\demo.py
python -m unittest discover -s . -p 'test_*.py' -v
デモはメモリ上のDBで完結します。テストの一部だけがPythonの一時フォルダへ実験用DBを作り、終了時に除去します。実在のDBへ接続せず、まず同梱の人工データで試してください。
中心となる判定は、同じトランザクション内で現在版を読み、入力の期待版と照合する部分です。次は公開コードからの抜粋です。
current = self.current(event["key"])
actual_revision = current["revision"] if current else 0
if event["kind"] == "proposal":
outcome = "pending"
elif not authorized:
outcome = "unauthorized"
elif revision != actual_revision:
outcome = "stale"
else:
outcome = "applied"
実コードは取消対象の有無、入力項目、同一IDの内容も追加で検査します。抜粋だけを本番へコピーせず、READMEとテストを含めて境界を確認してください。
取消と重複入力を18テストで確認した
9月29日のデモでは、4件のあとに同じ訂正を再入力し、取消を追加し、最後に取消前の版で更新しました。
訂正の再入力:replayed=true(履歴の追加なし)
取消:applied、kind=cancel、value=null、revision=3
取消前の版で更新:stale
最終的な履歴:6行
取消では値を空にしても、取消イベントと版3を確定値として残します。そのため、版2を前提にした古い入力で勝手に復活しません。一方、版3を読み直した別IDの set なら再設定できます。永久に変更不能にする仕組みではありません。
18テストは、訂正、古い版、提案、取消と再設定、重複入力、同一IDで異なる内容、未知の対象、入力不備、独立したキー、更新・削除トリガー、書込失敗時のロールバック、再オープン、2接続と2スレッドの書込を対象にし、すべて成功しました。同じ版を前提にした2スレッドの小さな試験では1件が applied、1件が stale でした。高負荷や複数端末同期へ一般化できる結果ではありません。
初回は14テスト中12件がエラーだった
9月5日の初回実装では、INSERTの8列に対して値のプレースホルダーを9個書き、14テスト中12件がエラーになりました。
sqlite3.OperationalError: 9 values for 8 columns
プレースホルダーを8個へ直し、取消や書込失敗を含むテストを追加して18件にしました。この失敗は今回の教材で起きた実装ミスです。既存のAI運用で同じ障害が起きた証拠でも、AIの長期記憶が壊れた事例でもありません。考え方だけでなく、実際に書込と失敗後の再利用まで動かしたことで見つけられた範囲の記録です。
実務で使う前に分ける4つの境界
1つ目は内容の正しさです。evidence は元資料へ戻る識別子を保存する欄で、教材は sample: から始まる架空IDを使います。参照先の存在や真偽は検査しません。誤った根拠を確定済みとして入れれば、誤った値を整然と保存します。
2つ目は認証と承認です。authorized=True は信頼済みの呼出側を仮定したテスト引数で、本人確認や承認画面ではありません。AIや入力文が自由にTrueを決められる接続では権限制御になりません。提案はTrueでも確定値にせず、採用時は人が現在版と根拠を確認して別IDの更新を作る必要があります。
3つ目は改ざん耐性です。履歴の更新・削除を止めるSQLiteトリガーは通常の誤操作対策です。DB所有者はトリガーを外せるため、改ざん不能な監査基盤ではありません。バックアップ、保存期間、削除要件、管理者権限は別に設計します。
4つ目は外部操作です。台帳で締切を取り消しても、実際の予定表や予約は変更されません。外部サービスへ接続する場合は、台帳の確定値、操作してよい権限、APIの成功、操作後の実状態を別々に確認します。
次の行動:架空データで古い入力を先に試す
まず、自分の業務に似せた架空のキーを1つ作り、初期値、訂正、訂正前の古い入力、提案、取消の順に投入します。current() が値・版・根拠・取消状態を返すか、未知のキーを推測で補完せず None にするかを確認してください。古い版で失敗したときに版番号だけを書き換えて再送すると、読み直しの意味がなくなるため、現在の根拠を確認して新しいイベントを作ります。
次に実データへ近づけるなら、認証済みの人だけが承認操作を作れる境界、根拠原本の保存場所、誤りを訂正する手順、外部操作との照合を1つずつ追加し、それぞれ失敗テストを用意します。LLMへ何を検索して渡し、回答が根拠と一致するかはその後の別検証です。本稿の18件成功を、AIの正答率や実運用の事故防止率へ読み替えないでください。
定期実行そのものの重複を調べたい場合は、定期タスクの二重実行と文字コードの検証を参照できます。台帳から取り出した値を外部操作へ渡す前に、権限・期限・重複を止める境界を考える場合は、AIエージェントの実行前ゲートを6ケースで検証が関連します。表紙は履歴の層を表した概念画像で、検証証拠には数えていません。