OKF/HTML

Open Knowledge in plain HTML. A note is a complete, self-describing HTML document — nothing to export, nothing to convert.

Instead of Markdown + YAML front matter or a JSON sidecar, each note is one HTML file: the body is readable content, the head carries identity and metadata, and ordinary links are the graph. The link graph is the knowledge graph. HTML is the data contract, not an export format.

This is an OKF/HTML note

One file. On the left, the document exactly as it is stored on disk. On the right, that same file opened in a browser — because it is a web page. This document was produced by the reference gem; the snippet that emitted it is in Getting started.

/n/sourdough-starter.html — stored on disk

<!DOCTYPE html>
<html lang="en">
<head profile="https://br3nt.github.io/okf/">
<meta charset="utf-8">
<title>Sourdough Starter</title>
<link rel="canonical" href="/n/sourdough-starter">
<meta name="uuid" scheme="UUID" content="d4013d9b-0ecc-4682-bb13-42e0b27746aa">
<meta name="created" scheme="ISO8601" content="2026-07-02T13:23:18+00:00">
<meta name="updated" scheme="ISO8601" content="2026-07-02T13:23:18+00:00">
<meta name="keywords" content="baking, bread">
</head>
<body>
<p>Feed daily. See <a rel="related" href="/n/bread">bread</a></p>
</body>
</html>
Hover the underlined partsEach dotted span explains what that piece of the document does.

opened in a browser — it's just a web page

Sourdough Starter

Feed daily. See bread.

No renderer required to read it — every device already ships one. A conforming tool goes further: it resolves the rel="related" edge, honours the uuid, and can rebuild its whole index from a folder of files like this. The bread link above is stubbed to the spec for this demo; in a real store it resolves to /n/bread.

Why HTML

The web platform already is a knowledge format. Links are the graph; <meta> and <link> are the metadata; the DOM is the query model; every device ships a renderer. OKF/HTML adds no new file type and no semantic-web toolchain — no RDF, OWL or SPARQL. Readers, human and machine, infer structure from HTML directly.

The format in 60 seconds

Six primitives, all made of ordinary HTML. Each is defined in the spec and covered by the language-neutral conformance fixtures.

Self-describing heads

Identity and metadata live in the <head>: a uuid, a canonical address, timestamps, tags — each typed with a scheme.

<meta name="created"
      scheme="ISO8601"
      content="2026-06-18T10:00:00Z">

Typed links (rel)

An edge is a link with a rel that names the target's role. Visible edges are <a> in the body; document-level ones are <link> in the head.

<a rel="related"
   href="/n/pantry-stock">
   pantry stock</a>

Backlinks (rev)

The outbound <a> is authoritative; its inverse is mirrored on the target as a rev link, so a file knows who links to it without the database.

<!-- mirrored in the target's head -->
<link rev="chapter"
      href="/n/chapter-one">

Collections

A named note that owns its membership as an <ol> (ordered) or <ul>. Members declare a role; the collection declares order. Tags are the flat, anonymous case.

<ol>
  <li><a rel="chapter"
        href="/n/tides">Tides</a></li>
</ol>

Vocabulary & profiles

A profile is itself an HTML document that defines the rel terms it governs (inverse, target-type, cardinality). <head profile> names the ones in force.

<head profile="https://br3nt
      .github.io/okf/">

Templates & associations

A note used as a prototype. Its head declares relationships to other templates — ported from Rails associations — so the "add related note" UI is generated, not hand-built.

<link rel="okf:has-many"
      href="/templates/chapter"
      data-as="chapter"
      data-ordered="true">
Only typed links (those with a rel) are mirrored as rev. A relation's human inverse name (chapter ↔ contents) is a vocabulary concern; the raw mirror always reuses the authoritative keyword. The reconciler keeps the mirrors, collection order, and delete-cascades in sync — authority wins on conflict.

Getting started

There is a working Ruby reference implementation today, and a language-neutral spec plus conformance fixtures for ports in any language that can parse HTML.

Ruby, now

One repo, two gems that version together: okf-html (the pure-Ruby format core — serializer, vocabulary, templates; no Rails, no I/O) and okf-html-rails (a mountable engine that wires the core into a host app). Reference gem v0.1.4, tracking spec draft v0.1.

# Gemfile — one repo, two gemspecs
gem "okf-html",       git: "https://github.com/br3nt/okf-html.git",
                       glob: "okf-html/*.gemspec"
gem "okf-html-rails", git: "https://github.com/br3nt/okf-html.git",
                       glob: "okf-html-rails/*.gemspec"

Hosts go through the Repository facade — never hand-write heads, slugs, or rev mirrors. This exact snippet produced the note at the top of the page:

require "okf/html"

repo = OKF::Repository.new(store: OKF::Store::Memory.new)
note = repo.create(
  title: "Sourdough Starter",
  content: %(<p>Feed daily. See <a rel="related" href="/n/bread">bread</a>.</p>),
  tag_names: ["baking", "bread"]
)
puts repo.store.read(note.uuid)   # => the document shown above

In Rails, include the container concern in whatever owns notes and get a scoped repository for free:

class WorkspaceNode < ApplicationRecord
  include OKF::Container
end

node.okf.create(title: "Idea", content: "<p>…</p>", tag_names: ["draft"])
node.okf.find(uuid_or_slug)        # => OKF::Note

Any other language

OKF/HTML is a format, not a framework. Anything that can parse HTML can read and write it — Python, JS, Go, Rust, whatever. Two artefacts get you there without reading Ruby:

  • SPEC.md — the format itself: heads, the link graph, rev mirrors, collections, vocabulary, templates, the reconciler, and a conformance checklist (reader / writer / reconciler).
  • fixtures/ — language-neutral test vectors, hand-written from the spec and executed against the Ruby implementation to confirm the expected output. render.yml gives attribute bags and the exact HTML a conforming renderer must emit; parse.yml gives stored documents and the fields a parser must extract. Compare rendered HTML as an exact string, including the final newline.

Bring up a port by making render.yml pass, then parse.yml. A case added to the fixtures adds coverage to the reference suite automatically, so the vectors and the implementation can't drift apart.

For agents

Because a note is plain, self-describing HTML, an agent is a first-class consumer: no bespoke parser, no export step. The format ships machine-readable entry points on purpose.

llms.txt

A short manifest pointing at the spec, the implementer and hypermedia guides, the agent skill, and the conformance fixtures — the map an agent reads first.

Machine-readable spec

SPEC.md is written to be followed literally; the fixtures are executable ground truth an agent can check its output against.

Agent skill

An okf-html skill (.claude/skills/okf-html/ in the repo) teaches an agent to author and manage notes — typed links, collections, templates, vocabulary — as plain HTML.

Hypertext as the API

The engine leans on HTML as the wire format end to end: notes are documents, and the editor and graph ship as no-build components. HTML is what crosses every boundary.

Profile

This URL — https://br3nt.github.io/okf/ — is the OKF/HTML base profile: the format's stable identifier. Every conforming document declares it on <head profile="…"> (composed with any user-defined profiles), and it is embedded verbatim in every rendered note — it is not a placeholder and does not change.

A document that references this URL is asserting: "my rel values, rev mirrors, collections and metadata mean what the OKF/HTML spec says they mean." The human-readable profile vocabulary page lives here; the canonical, authoritative definition of the terms is the spec.

Canonical spec: SPEC.md in the okf-html repo. Base relation catalogue and the naming rule: SPEC §6. The <head> profile declaration: SPEC §2.2.