Open Knowledge Formatに手元のmd群を適合させる|必須はtypeだけ

「Open Knowledge Format」「One Required Field」の文字と、「Frontmatter」と添えられた散らばった markdown のカードが、「Conformance」と添えられた 3 つの開いたゲートを通って整列し、右端では「No Linter」の文字の横でスタンプが押されないまま置かれているイラスト AIエージェント

はじめに

AI エージェント向けに書き溜めていた markdown 群を、Google Cloud が公開している Open Knowledge Format(OKF)v0.2 に適合させました。対象は 35 本で、足したのは必須フィールドの type だけです。

エージェントが読み書きし続けるファイルに対して「何から作ったか」「まだ現役か」を frontmatter から答えられる状態にするためです。frontmatter は、markdown ファイルの先頭に --- で挟んで書く YAML のメタデータ領域を指します。

本記事のまとめ

  • 適合条件は 3 つだけで、必須フィールドは type 1 つである。推奨フィールドを足さなくても適合する。
  • 独自の frontmatter 規約を OKF のキーと並べたところ、対応先が無かったのは type だけだった。これは私がキーを並べて出した見立てである。
  • バンドル相対のパスはバンドルルート基準である。どこをバンドルルートに置くかで、適合条件の及ぶ範囲も変わる。
  • 適合を確かめる linter は、2026 年 10 月 11 日時点の v0.0.1 では何も検査しなかった。機械で確かめるなら自分で用意する前提になる。
ディレクトリツリー全体が Knowledge Bundle で、1 つの .md ファイルが frontmatter と本文の 2 層からなる Concept であることを示した図
OKF の実体。ディレクトリツリーが Knowledge Bundle、個々の .md が Concept にあたる。frontmatter のうち必須は type の 1 行だけで、予約名は index.md と log.md の 2 つ

Open Knowledge Formatとは|必須はtypeだけで適合条件は3つ

OKF は markdown と YAML frontmatter のディレクトリを知識の表現形式として定めた仕様で、必須フィールドは type 1 つだけです。

Google Cloud が 2026 年 6 月 13 日に発表した、特定のベンダーに依存しない形式です。実体はディレクトリで、ツリー全体を Knowledge Bundle、個々の .md を Concept と呼びます。Concept ID は .md を除いたパスそのものなので、識別子を別に振る必要はありません。

予約されたファイル名は 2 つです。

  • index.md — そのディレクトリの目次。
  • log.md — 更新履歴。

この 2 つ以外の .md はすべて Concept として扱われます。

適合条件は次の 3 つだけです。

条件内容
1予約名以外のすべての .md が、解析できる YAML frontmatter を持つ
2すべての frontmatter が、空でない type を持つ
3予約名のファイルがあるなら、仕様が定める構造に従う

裏返すと、拒否してよい理由は極端に狭く定められています。仕様は、次の 5 つを理由にバンドルを拒否してはならないと明記しています。

  1. 任意の frontmatter フィールドが無いこと。
  2. 知らない type の値が来ること。
  3. 知らないキーが混ざっていること。
  4. ファイル間のリンクが切れていること。
  5. index.md が置かれていないこと。

v0.2 では、その Concept が何から作られたかを示す provenance(sources)、誰が書いて誰が確かめたかを示す trust(generated / verified)、まだ現役かを示す lifecycle(status / stale_after)が、仕様の一級の要素になりました。

仕様の全文は SPEC v0.2 にあります。発表時の狙いは Google Cloud のブログで読めます。

左に適合条件の 3 つ、右に未知の type・未知のキー・壊れたリンク・index.md の欠如では拒否してはならないことを並べた図
OKF v0.2 の適合条件は 3 つだけで、拒否してよい理由は極端に狭い。独自キーを足しても、リンクを張らなくても、目次を置かなくても適合は壊れない

独自のfrontmatter規約と並べたら、欠けていたのはtypeだけだった

もともと持っていた 5 つのキーを OKF v0.2 のキーと並べたところ、対応先が無かったのは type だけでした。

変換前の frontmatter はこうなっていました。

---
status: active
updated_at: 2026-10-10
review_after: 2027-01-17
scope: infra
sources:
  - issue-41
  - issue-43
  - issue-72
---

OKF の語彙に寄せたあとが、こちらです。

---
type: Operational Guardrail
description: IAM 付与の具体則。Group 経由、ロールの選び方
status: stable
updated_at: 2026-10-10
stale_after: 2027-01-17T00:00:00Z
scope: infra
sources:
  - resource: issue-41
  - resource: issue-43
  - resource: issue-72
---

キーの対応は次のとおりです。

独自のキーOKF v0.2違い
statusstatus名前は一致。値の語彙だけ違う
updated_atgenerated.at対応先はあるが変換しなかった(後述)
review_afterstale_afterほぼ同じ概念。絶対時刻が要る
sourcessources名前は一致。形が違う
scope追加キーとして許容そのまま残せる
—type対応先が無かった唯一のキー

scope は OKF に無いキーですが、仕様が producer 側の追加キーを明示的に認めているため、そのまま残せます。

対応先があるのに変換しなかったキーが 1 つだけあります。updated_at です。OKF の generated は「誰が書いたか」を必須に持ちますが、過去のファイルの書き手は分かりません。一律に埋めれば記録ではなく捏造になるので、独自キーのまま残しました。仕様は generated が無くても拒否してはならないと定めています。

「ほとんど一致していた」という感触は私のもので、事実は「対応表を作ったら 5 つのうち 4 つに行き先があった」というところまでです。

もともと持っていた 5 つのキーのうち 4 つに OKF v0.2 の対応先があり、足りなかったのは type だけだったことを示す対応図
独自の frontmatter 規約と OKF v0.2 のキーを並べた対応。足りなかったのが type だけというのは私がキーを並べて出した見立てで、仕様が保証するものではない

適合させる手順|typeの付与からindex.mdの書き換えまで

手順に入る前に、バンドルルートをどこに置くかを決めます。ここが後の作業範囲をすべて決めるためです。

適合条件 1 と 2 は「ツリー内のすべての .md」に及びます。リポジトリルートをバンドルルートにすると、対象外にしたいファイルまで type が必須になります。仕様はサブディレクトリを配布形態として認めているので、そこで範囲を絞れます。

もう 1 つ、/ 始まりのパスが指す先も変わります。/ はバンドルルート基準で、リポジトリルート基準ではありません。バンドルの外にあるファイルを指したいときは、相対パスを使います。

バンドルルートをリポジトリルートに置いた場合とサブディレクトリに置いた場合で、スラッシュ始まりのパスの解決先と適合条件の及ぶ範囲が変わることを並べた図
バンドルルートの位置でスラッシュ始まりのパスの解決先が変わる。リポジトリルートに置くと適合条件がツリー内のすべての .md に及び、対象外にしたいファイルまで type 必須になる

frontmatterをOKFの語彙に寄せる

frontmatter の中の作業は 5 つで、同じ場所をまとめて書き換えるので一度に済ませます。

  1. type を全ファイルに付ける。
  2. status の値を draft / stable / deprecated の 3 語に寄せる。
  3. review_after を stale_after に変える。
  4. 値がファイル名と同じだった name を消す。
  5. 全ファイルに description を付ける。

type の分類は 1 種類で揃えました。ドメインの軸は scope が持っているので、type もドメインで切ると同じ軸を 2 か所に持つことになります。これは私の判断で、仕様は type の値を中央登録しません。

仕様には「status が無ければ stable とみなす」と書かれているので、元の active 22 本は行を消しても結果は同じでした。読んで分かるほうがよいので stable と明示しています。

stale_after には絶対時刻が要るため、日付だけだった値に T00:00:00Z を足しました。

sourcesはマップ形式にする

sources の各エントリは、resource キーを持つマップでなければなりません。独自の規約では、ただの文字列のリストでした。

書き換えてみると、値は 3 つの形に分かれました。

sources:
  - resource: issue-41
  - resource: issue-211
    title: CI 自動化を廃止し /context-update に一本化
  - resource: ../../../scripts/context/

1 つ目の issue-41 は追えるパスではありませんが、仕様は resource に「範囲を指す記述」を書くことも認めているので通ります。2 つ目は元が括弧で注記を付けた 1 本の文字列で、注記を title に分けました。3 つ目はバンドルの外なので相対パスです。

この変換には副産物がありました。文字列をパスに直したところ、リンク先が解決しない参照が出てきました。調べると、別のリポジトリの作業記録を指していたものでした。文字列のまま並んでいるあいだは、追える参照と追えない参照が同じ見た目をしていて、区別が付きませんでした。

index.mdはfrontmatterが禁止で、本文の構造まで決まっている

予約ファイルの index.md には frontmatter を置けません。例外は、バンドルルートの index.md にだけ書ける okf_version です。

本文の形も決まっていて、見出しごとにグループ化したリンクリストにします。各項目には、リンク先の description を添えることが推奨されています。

---
okf_version: "0.2"
---

# プロジェクト

* [project.md](project.md) - 目的・対象範囲・環境一覧
* [current/status.md](current/status.md) - 現在状態。自動生成・Git 管理外

# 実行ガードレール

* [knowledge/](knowledge/) - 編集中・実行直前に目に入らないと事故るもの

手元の index.md は表形式で、一覧したファイルにリンクが張られていませんでした。リンクリストに書き換えて、適合条件の 3 つ目を満たしました。

適合を確かめるlinterは、v0.0.1では何も検査しなかった

適合を機械で確かめようと okf-lint を動かしましたが、2026 年 10 月 11 日時点の v0.0.1 は違反を 1 件も報告しませんでした。okf-lint は Google Cloud の公式ツールではなく、個人が公開している npm パッケージです。

$ npx okf-lint ./context
okf-lint
Linting ./context ...

$ echo $?
0

バナーを 2 行出して、終了コード 0 で終わります。これだけでは「問題 0 件」とも「対象 0 件」とも読めるので、切り分けに 2 つ試しました。

  • 出力形式に JSON を指定しても、JSON は出力されなかった。
  • frontmatter が無いファイル、type が無いファイル、列挙にない status を混ぜたバンドルを検査させても、違反 0 件で終了コード 0 だった。

README には、複数のフラグ、1 problem (0 errors, 1 warning) という形の出力例、終了コード 0 / 1 / 2 の仕様まで書かれています。README の記述と実装が一致していません。リポジトリ名とスター数だけを見て CI に入れると、何も検査していないのに緑になります。

npm に公開されているのは v0.0.1 の 1 版だけで、公開日は 2026 年 7 月 4 日です。GitHub 側の最終更新も 2026 年 6 月で止まっています。Claude Code 向けのツールキットも別にありますが、個人のリポジトリでした。

入手先は GitHub のリポジトリと npm の配布ページです。保守の状況は読む時点で確かめてください。

結局、適合条件の 3 つを検査するスクリプトを自分で書いて確かめました。

README に書かれたフラグと出力例に対して、実際の実行ではバナー 2 行と終了コード 0 しか返らなかったことを並べた図
okf-lint v0.0.1 を 2026 年 10 月 11 日に動かした結果。README が謳うフラグと出力形式に対し、わざと違反させたバンドルを渡しても出力はバナー 2 行と終了コード 0 だけだった

自作チェッカーでつまずいた点|YAMLパーサが日時を変換する

自分で書いたチェッカーは、最初の実行で stale_after を全件 NG と報告しました。ファイル側は最初から正しく、誤っていたのはチェッカーのほうです。

原因は、YAML パーサが 2027-01-10T00:00:00Z を文字列ではなく datetime に自動変換することでした。文字列として正規表現を当てていたので、1 件も一致しません。

import yaml

v = yaml.safe_load("stale_after: 2027-01-10T00:00:00Z")["stale_after"]

print(type(v).__name__)    # datetime
print(isinstance(v, str))  # False
print(str(v))              # 2027-01-10 00:00:00+00:00
print(v.tzinfo is None)    # False

文字列に戻すと区切りが T ではなく空白になり、末尾も Z ではなくなります。ISO 8601 の形を正規表現で確かめる書き方は、ここで崩れます。tzinfo が付いているかどうかで判定するように直して、違反 0 件になりました。

適合させる前と後で変わったこと

書式を仕様に寄せただけで、本文そのものは 1 文字も変えていません。それでも、数えられる差分が出ました。35 本のうち、目次 2 本と自動生成の 1 本を除いた 32 本が frontmatter を持つ concept です。

項目前後
type を持つ concept0 / 32 本32 / 32 本
description を持つ concept0 / 32 本32 / 32 本
値がファイル名と同じだった name12 本0 本
目次から張られたリンク2 本35 本(解決しないもの 0 本)
親の目次が抱えていた表の行40 行0 行

いちばん効いたのは目次です。前は親が knowledge の一覧を 29 行の表で抱え、サブディレクトリ側にも別の一覧ファイルがありました。そちらは 30 本中 6 本しか載っておらず、どこからも参照されていません。予約名の index.md に転換したら、親は 1 行で指すだけになりました。

変換前は親の目次が 29 行の表を持ちサブディレクトリの README がどこからも参照されていなかったのに対し、変換後は親が 1 行のリンクで指し、サブの index.md が 29 本を説明つきで列挙している構造を並べた図
目次の構造の前後。29 行の表が 1 行のリンクになり、同じ一覧を 2 か所で持つ状態が解消した。目次から張られたリンクは 2 本から 35 本に増え、解決しないリンクは 0 本だった

毎セッション自動で読み込む入口の index.md は、7,209 バイトから 2,945 バイトになりました。ただし一覧がサブの目次へ移っただけで、2 つを足すと前とほぼ同じです。type と description を足したぶん、ディレクトリ全体はむしろ 5% ほど増えました。トークンの消費量は測っていませんし、仕様もそこを目的にしていません。仕様が掲げているのは、信頼してよいかを frontmatter から判断できることと、別のツールや別の組織に渡しても、時間が経っても同じファイルがそのまま読めることです。

得られたものは、「適合した」という状態そのものより、変換の過程で独自規約の穴が見えたことでした。追えない参照が混ざっていたことも、日付が絶対時刻になっていなかったことも、キーを仕様に合わせようとしなければ気づけません。

一方で、期待していた効果のうち 1 つは得られませんでした。作業を始めた動機は「仕様に寄せれば外部のツールがそのまま使える」というものでしたが、少なくとも linter については当てになりませんでした。

ファイル間の相互リンクは 0 本のままです。書式ではなく中身の作業なので、別に切り出しました。

まとめ

独自の frontmatter 規約があるなら、OKF に寄せるために足すものは少なく済みます。

  • 適合条件は 3 つだけで、必須フィールドは type 1 つである。拒否してよい理由も極端に狭い。
  • 先に決めるのはバンドルルートの位置である。/ 始まりのパスはバンドルルート基準で、適合条件の及ぶ範囲もここで決まる。
  • 適合を機械で確かめる手段は、試した時点では揃っていなかった。自分で用意する前提で計画する。

まずは自分のリポジトリの frontmatter を OKF のキーと並べてみてください。足りないものがすぐに分かります。

なお、linter のバージョンと提供状況、仕様の版は変わります。実際に試すときは、最新を公式ドキュメントで確認してください。