vault2okf
Convert an Obsidian vault into an Open Knowledge Format (OKF) bundle, then check and repair any OKF bundle before an agent or a teammate reads it.
OKF is a folder of markdown files with YAML frontmatter, where each file is one concept and concepts link to each other with normal markdown links. An Obsidian vault is close to that, but its [[wikilinks]] only work inside Obsidian and its notes carry no type. vault2okf closes the gap and then keeps the bundle healthy.
vault2okf convertturns a vault into a conformant bundle: wikilinks become real links, embedded files are copied, frontmatter and a per-folderindex.mdare written. The vault is never modified, and re-runs change only what changed.vault2okf validatechecks any bundle against the OKF v0.2 spec: frontmatter, reserved files, trust and lifecycle fields, broken links and heading anchors, leftover wikilinks, orphan notes, and file names that break on Windows or macOS. It prints plain text, JSON or GitHub annotations.vault2okf fixrepairs the common problems in place: missingtype,titleanddescription, tags written as one string, wikilinks, an unquotedokf_version.vault2okf init,schemaandrulesscaffold a bundle, print a JSON Schema for concept frontmatter, and list every rule.
It also works as a Python library, a GitHub Action and a pre-commit hook. Settings can live in the repository, in vault2okf.toml or pyproject.toml.
Install
pipx install vault2okf # or: uv tool install vault2okf
Needs Python 3.10 or newer.
Quick start
vault2okf convert ~/Documents/MyVault ./knowledge
vault2okf validate ./knowledge
$ vault2okf validate knowledge
Home.md
L13 warning OKF030 link target not found: /Runbooks/deploy.md
todo.md
warning OKF031 no document in the bundle links to this concept
L1 error OKF001 file must start with a `---` YAML block
not conformant: 4 concepts, 10 links, 1 error, 2 warnings
Exit codes: 0 conformant, 1 validation failed, 2 usage error.
Why vault2okf
okf-cli also converts markdown to OKF. It is built for plain markdown folders and adds commands to browse a bundle. vault2okf is built for Obsidian vaults and for catching and fixing problems. Both tools were run in September 2026 on the vault in examples/vault and on a test bundle with eight planted problems:
| vault2okf 0.1.0 | okf-cli 0.6.1 | |
|---|---|---|
[[wikilinks]], aliases, heading links, ![[embeds]] |
converted to markdown links | left unchanged |
| Embedded images and files | copied into the bundle | not copied |
Frontmatter, type and index.md checks |
yes | yes |
Timestamp, status and generated checks |
yes | no |
Broken links, leftover wikilinks, orphans, log.md dates |
reported | not reported |
| Planted problems reported (out of 8) | 8 | 1 |
| Repairs problems in place | yes (fix) |
no |
| JSON and GitHub annotation output | yes | no |
list and read commands, generated AGENTS.md |
no | yes |
The two install different commands (vault2okf and okf), so they can sit side by side.
The validator finds no errors in the four sample bundles in Google's OKF repository. One of them, stackoverflow, gets eight warnings because its tags are written as one string instead of a list; vault2okf fix rewrites exactly those eight lines.
What convert does
| In the vault | In the bundle |
|---|---|
[[Note]], [[folder/Note]] |
[Note](/folder/Note.md), resolved the way Obsidian does (full path, then unique name, then closest folder) |
[[Note|alias]], [[Note#Heading]], [[#Heading]] |
[alias](/Note.md), [Note > Heading](/Note.md#heading), [Heading](#heading) |
[[ship]] where no note is named ship but one lists it in aliases: |
a link to that note, listed in the report (Obsidian itself leaves such links unresolved) |
![[image.png]] |
, with the file copied into the bundle |
![[Other note]] |
a normal link to the note |
[[missing]] |
plain text, listed in the report |
%% comments %% |
removed, because Obsidian never shows them and they often hold private notes; --keep-comments keeps them |
tags: and inline #tags |
one tags list; anything inside code, links, HTML or comments is ignored |
no type |
Note, or set one with --type-map or --type-from-folder |
first # Heading |
title, falling back to the file name |
| first paragraph | description, one sentence of at most 200 characters; notes without a paragraph use their first callout, list item or section headings |
| file modified time | generated: { by: vault2okf/x.y.z, at: ... } |
other frontmatter (aliases, cssclasses, ...) |
kept as is |
notes named index.md or log.md |
renamed index-note.md / log-note.md, since OKF reserves those names |
names Windows rejects (What? Is: this.md, CON.md) or that differ only by case |
renamed as little as possible (What Is- this.md, CON_.md, case (2).md) with every link updated |
.obsidian/, hidden files, symbolic links |
skipped |
It also writes an index.md in every folder (notes grouped by type, with okf_version: "0.2" in the root one) and a root log.md. Every rename, alias link and unresolved link is listed in the report.
vault2okf convert vault out \
--type-map 'Daily/*=Daily Note' \
--type-map 'Runbooks/*=Runbook' \
--exclude 'Private/*' \
--attachments referenced # referenced (default) | all | none
Updating a bundle
Run the same command again with --force after the vault changes:
$ vault2okf convert vault knowledge --force
updated knowledge: 1 concept added, 2 changed, 0 removed, 41 unchanged
Only files whose content changed are written, so unchanged files keep their timestamps and a git diff shows just the real changes. generated.at moves only when a note's content changes, not when a fresh checkout resets file times. log.md keeps its history and gains an entry per change, newest first:
## 2026-09-26
* **Creation**: Added [Kubernetes](Hosting/Kubernetes.md).
* **Update**: Changed [Vercel](Hosting/Vercel.md).
* **Removal**: Removed `Hosting/Heroku.md`, which is no longer in the vault.
Safety: convert never follows symbolic links, so no file from outside the vault can end up in the bundle. It refuses to write inside the vault or into a non-empty folder it did not create, and it only ever removes files listed in its own .vault2okf.json manifest, after the new files are written. --dry-run reports without writing anything.
What validate checks
vault2okf rules prints the full table. Severity follows the spec: things a bundle must do are errors, things consumers must tolerate are warnings.
| Code | Severity | Meaning |
|---|---|---|
| OKF001 | error | Concept has no frontmatter block |
| OKF002 | error | Frontmatter is not valid YAML |
| OKF003 | error | type missing, empty or not a string |
| OKF004 | error | Frontmatter is not a mapping |
| OKF010 | error | index.md carries frontmatter other than a root okf_version |
| OKF020 | error | log.md date heading is not YYYY-MM-DD |
| OKF040 | error | Malformed generated, verified, sources, status, stale_after or attested-computation fields, or a timestamp without a UTC offset |
| OKF030 | warning | Broken link (the spec says consumers tolerate this) |
| OKF031 | warning | Orphan concept, nothing links to it |
| OKF033 | warning | Leftover [[wikilink]] |
| OKF034 | warning | File or folder name Windows rejects, or two names that differ only by case |
| OKF035 | warning | Link to a #heading that does not exist in its target (GitHub's anchor rules) |
| OKF041 / OKF042 | warning | Missing recommended title / description |
| OKF043 | warning | generated.by or verified[].by is not <producer>/<version>, human:<id> or process:<id> |
| OKF044 | warning | Recommended field of the wrong type, such as tags written as one string |
| OKF050 | warning | stale_after has passed |
A concept containing only type is fully conformant, and unknown types and keys are never errors.
vault2okf validate bundle --strict # warnings fail the run too
vault2okf validate bundle -i OKF031 -i OKF042 # skip rules
vault2okf validate bundle --format json # machine readable
vault2okf validate bundle --format github # inline annotations in Actions
vault2okf validate bundle -x 'drafts/*' # skip paths
Fix a bundle
vault2okf fix works on any OKF bundle, whoever produced it, and changes files in place. Commit first, or preview with --dry-run.
$ vault2okf fix knowledge
Home.md: set type to 'Note', added title, added description, converted 1 wikilink
Hosting/Vercel.md: added description, wrote tags as a list, converted 1 wikilink
fixed 7 problems in 2 files
left for you:
Hosting/Vercel.md: [[Docker]] matches no file in the bundle
validation
Hosting/Vercel.md
L7 warning OKF033 [[Docker]] is a wikilink; use [text](/path.md)
conformant: 2 concepts, 2 links, 0 errors, 1 warning
It adds a missing type (--default-type, Note unless set), title (first heading, else the file name) and description (first sentence), writes string tags as a list, turns numbers into text where text belongs, converts wikilinks that match a file in the bundle, and quotes an unquoted okf_version. Only the lines that change are rewritten, so YAML comments, other keys and the file's line endings are kept. Anything that needs a person, like invalid YAML or a link to a note that does not exist, is listed instead of guessed.
Settings file
Put defaults in vault2okf.toml, or under [tool.vault2okf] in pyproject.toml. The nearest one at or above the current folder is used, and flags on the command line always win. --config picks a file by path.
[convert]
type-from-folder = true
type-map = { "Daily/*" = "Daily Note", "Runbooks/*" = "Runbook" }
exclude = ["Private/*"]
attachments = "referenced" # referenced | all | none
keep-comments = false
[validate]
strict = true
ignore = ["OKF031"]
exclude = ["drafts/*"]
[fix]
default-type = "Note"
In pyproject.toml the sections become [tool.vault2okf.convert], [tool.vault2okf.validate] and [tool.vault2okf.fix]. Unknown sections, settings and rule codes are errors, so a typo never passes silently.
GitHub Action
- uses: actions/checkout@v4
- uses: abhishekacharya200/vault2okf@v0.1.0
with:
path: knowledge
strict: "true"
pre-commit
repos:
- repo: https://github.com/abhishekacharya200/vault2okf
rev: v0.1.0
hooks:
- id: okf-validate
args: [knowledge]
Library
from vault2okf import ConvertOptions, convert_vault, fix_bundle, validate_bundle
convert_vault("MyVault", "knowledge", ConvertOptions(type_from_folder=True))
fix_bundle("knowledge")
report = validate_bundle("knowledge", ignore=["OKF031"])
for d in report.diagnostics:
print(d.code, d.path, d.line, d.message)
vault2okf schema > okf-frontmatter.schema.json gives editors a JSON Schema for concept frontmatter.
Known limits
- Indented (4-space) code blocks are not detected by the converter, so a
[[link]]inside one is still converted. - Block references (
[[Note#^id]]) lose the block part and link to the note. - Dataview, Templater and other plugin syntax is copied as is.
fixcannot repair frontmatter that is not valid YAML; it lists those files instead.
Development
uv sync
uv run pytest --cov=vault2okf
uv run ruff check . && uv run ruff format --check . && uv run mypy src
See CONTRIBUTING.md. This project is not affiliated with Google. OKF is an open specification published by the Google Cloud Data Cloud team.
License
Release files for vault2okf 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vault2okf-0.1.0.tar.gz | 50.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vault2okf-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 94.0 kB
Release files / vault2okf-0.1.0.tar.gz
| Download URL | vault2okf-0.1.0.tar.gz |
|---|---|
| Size | 50.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0e2f801ab16c807a0d92bed13671cac163e7af366e4c9ee0cc8be4bb336c352d
|
|
BLAKE2b-256 checksum How to use checksums |
69a5d28ef1807d3d95b39f66be56c1a4e99e48b87136ca06d9d7bb7f49c2d7e5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency logRelease files / vault2okf-0.1.0-py3-none-any.whl
| Download URL | vault2okf-0.1.0-py3-none-any.whl |
|---|---|
| Size | 43.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c40954e41ae32a8e686e497ec70a531ddccb26386e11000cbfe4b2ee19c563de
|
|
BLAKE2b-256 checksum How to use checksums |
005cdc79e06c79df590933e89e132785f58e19e998315a1adcbbcc56afeeae3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.
Transparency log