M07 β OKF for Codebases
Learning Objectives
- Translate the OKF concept shape from Google's data-asset use case to services, modules, and APIs.
- Author a complete
type: Serviceconcept with Responsibilities, Dependencies, and Citations sections. - Explain why cross-links form a dependency graph richer than the filesystem tree β and why it's curated, not verified.
- Use the okf CLI:
init,hook install,search, andlint(13 rules), plus the Go library primitives. - Recognize and prevent the governance failure modes: inconsistent body structure, unattested computations, and type drift.
The Pivot: From BigQuery Tables to Your Repo
Google's reference use case for OKF is BigQuery metadata: their demo agent walks a dataset and drafts one concept per table. Useful β but this course is about codebases. The pivot that matters for software teams is recognizing that the exact same concept shape maps directly onto services, modules, and APIs. You swap two things and keep everything else:
resource:stops being a BigQuery console URL and becomes a repo path.- The
# Schemabody section becomes# Responsibilitiesand# Dependencies.
Watch the morph, field by field:
This pivot is why a one-page data-metadata spec became an AI-coding-agent story at all. A coding agent that can read "billing-service owns Invoice, calls customer-service, publishes to notifications-service" before touching the repo skips exactly the exploration phase M00 measured at 147 file reads. One concept file β a few hundred tokens β replaces a discovery crawl that costs tens of thousands.
The Service Concept, In Full
Here is the canonical service concept β the one this course's orderflow bundle is built around. Read it top to bottom once as a human, then we'll re-read it as an agent would:
---
type: Service
title: billing-service
description: Handles subscription billing, invoicing, and payment webhooks.
resource: https://github.com/acme/monorepo/tree/main/services/billing
tags: [billing, payments, python]
timestamp: 2026-06-30T09:12:00Z
---
# Responsibilities
Owns the `Invoice` and `PaymentEvent` domain models. Consumes Stripe webhooks via
[stripe-webhook-handler](/services/billing/stripe-webhook-handler.md).
# Dependencies
- Calls [customer-service](/services/customer-service.md) to resolve account state.
- Publishes events consumed by [notifications-service](/services/notifications-service.md).
# Citations
[1] [Billing runbook](https://wiki.internal/billing-runbook)Now the agent's reading. The frontmatter answers "is this file relevant?" in ~40 tokens: it's a Service, it's about billing and payments, it lives at that repo path, it was last updated June 30. The # Responsibilities section answers "what does it own?" β the domain models an agent must not casually rename. The # Dependencies section answers "what breaks if I change it?" β with links the agent can follow for exactly as deep as the task requires. The # Citations section anchors the narrative to human-maintained sources, which is what separates documented knowledge from plausible-sounding text (M08 automates keeping this honest).
One production tip from the field carries more weight than any individual field: consistency of body structure across concept files matters more for agent consumption than any single field does. If every Service concept has the same three sections in the same order, an agent can parse fifty of them mechanically. If each author invents their own headings, every read becomes an interpretation problem. Write a template; enforce it in review.
Cross-Links: A Dependency Graph Richer Than the Tree
The links in that concept aren't decoration. Bundle-relative linksMarkdown links whose target is a path inside the bundle (e.g. /services/customer-service.md). They connect concepts regardless of where the described code lives in the actual repo tree. turn a flat directory into a dependency graph that is richer than your filesystem's parent/child hierarchy: billing-service formally points at everything it calls and everything it emits to, independent of where those services physically sit in the repo tree. A filesystem can only express "contains." Links express "calls," "publishes to," "consumes," "supersedes" β whatever the prose around them says.
Do not treat this link graph as a substitute for the static analysis you built in Track 2. OKF links are whatever a human or enrichment agent wrote β the spec even requires consumers to tolerate broken ones. Graphify's AST edges are verified ("the code demonstrably calls this"); OKF's links are curated ("we assert this relationship matters"). You want both: the curated summary for orientation and intent, the verified graph for impact analysis. When they disagree, the AST is right about structure and the bundle is right about why β and the disagreement itself is a maintenance signal (M08).
index.md as the Agent's Pre-Flight Read
M06 introduced index.md as a token-budget lever. For codebases it has a sharper job description: it is the file a coding agent reads before touching the repo. Google's framing is precise: the bundle acts as "a pre-flight context load, not a search index you query mid-task." Here's a root index for a billing domain:
# Billing Domain
* [billing-service](services/billing-service.md) - subscription billing, invoicing, webhooks
* [customer-service](services/customer-service.md) - account and subscription state
* [Runbooks](runbooks/) - on-call playbooks for billing incidentsThree lines. That single file replaces a full-repo re-scan or a re-embed. A coding agent reads it, knows the territory, and loads only the concept the task touches. A data assistant consults it before generating SQL. The discipline from M06 applies doubly here: title plus one-line description, pulled straight from each concept's frontmatter, nothing more.
The okf CLI: Tooling You Don't Have to Build
OKF is a spec, so the tooling is third-party β and the reference CLI proves the shape works. The okf CLI (written in Go, Apache-2.0, by the superops-team, independent of Google) covers the full loop:
# Scan the current repo and scaffold a .okf/knowledge bundle
okf init
# Install a git hook so the bundle auto-updates on every commit
okf hook install
# Query concepts by keyword
okf search -q "billing"
# Enforce the 13 built-in spec-compliance rules
okf lintEach command maps to a phase of the lifecycle this course teaches. init is EXTRACT-and-scaffold. hook install is the whole MAINTAIN pitch in one line: on every commit, rescan and refresh the affected concepts so the graph never drifts far before something forces it back into sync (M08 builds this pipeline by hand so you understand it; M11 shows how it fails silently). search is the minimal SERVE story. And lint β 13 built-in spec-compliance rules β is your strictness layered on top of the spec's leniency.
For orchestration code, the same project exposes Go primitives you can bolt into an agent pipeline:
bundle, err := okf.LoadBundle(".okf/knowledge", nil)
if err != nil {
log.Fatal(err)
}
results := bundle.Search("billing")
report := lint.LintBundle(concepts, lint.DefaultConfig())The division of labor is exact: bundle.Search is what your orchestrator calls before dispatching a sub-agent (which concepts does this subtask need?), and lint.LintBundle is what your CI calls before trusting the bundle enough to let agents act on it. Run lint as a hard gate: given how lenient the spec is, lint is the only thing standing between "technically conformant" and "actually useful."
One more ecosystem piece worth knowing: Kiso, a third-party publishing engine that compiles an OKF bundle into a static site β HTML for humans, plus auto-generated llms.txt and sitemap.xml for crawler-style agents β designed to run in CI on every merge so the published bundle never lags the source of truth.
Governance: Attested Computations and Type Drift
The attested computation
The spec includes a concept type worth singling out: the attested computationAn OKF concept documenting a sanctioned, checkable way to COMPUTE a value β the code path or query that produces it β as distinct from prose describing what the value means. β a sanctioned, checkable way to compute a value, distinct from just documenting what the value means. "WAU means weekly unique actives" is a description; "WAU is computed by services/orders/metrics.py::weekly_active_users, which excludes these three tester accounts" is an attestation. For governed metrics that need a single source of truth for how they're calculated β revenue, churn, anything finance signs off on β give the computation its own concept file rather than folding logic into a general description. orderflow's WAU concept does exactly this.
Type drift
Because type is producer-defined with no external registry, multiple teams contributing to one bundle will drift: one team writes API Endpoint, another Endpoint, a third Route. Nothing in the spec prevents it β three different strings are all conformant, and every agent filtering on type now silently misses two-thirds of the endpoints. Only your own lint rules and review conventions hold the line: pick a canonical type list, publish it in the bundle's root, and make lint reject strays. This is the first of the governance problems that get harder at scale β a bundle maintained by three people stays coherent by convention; one touched by thirty needs enforcement (M11 returns to this).
"The links give me impact analysis." β They give you claimed impact. Verified impact analysis comes from the AST graph (Track 2). Use links for orientation, edges for guarantees.
"We'll fix type naming later." β Later never comes cheaper. Every agent query written against the drifted types bakes the inconsistency in. Canonicalize on day one; it's one lint rule.
"A bundle is documentation, so writers own it." β A consumed bundle is infrastructure: it has an SLA (freshness), a test suite (lint), and consumers that break when it lies. Treat it with code discipline β that's the entire premise of the next module.
Walk it, step by step
Decide what deserves a concept file. The step to slow down on is the comparison between a concept that earns its tokens and one that just restates a function signature.
Code Walkthrough: Lint with Dangling-Link Detection
M06's validator checked one rule. Production lint needs at least two more: links must resolve and types must be canonical. Chunk 1 β collect the link targets. WHAT: regex out every relative .md link from each body. WHY: the spec tolerates dangling links, so nothing else will ever flag them. GOTCHA: resolve targets relative to the linking file, then normalize β ../tables/x.md from two different folders points at two different files.
import re, sys
from pathlib import Path
import frontmatter
CANONICAL_TYPES = {"Service", "metric", "table", "runbook", "ADR"}
LINK_RE = re.compile(r"\[[^\]]*\]\(([^)#]+\.md)\)")
def lint(root: Path) -> int:
errors = 0
for path in sorted(root.rglob("*.md")):
if path.name in {"index.md", "log.md"}:
continue
try:
post = frontmatter.load(path)
except Exception as exc:
print(f"ERROR {path}: unparseable ({exc})"); errors += 1; continue
ctype = post.metadata.get("type")
if not ctype:
print(f"ERROR {path}: missing 'type'"); errors += 1
elif ctype not in CANONICAL_TYPES: # stricter than the spec β deliberately
print(f"ERROR {path}: non-canonical type {ctype!r}"); errors += 1
for target in LINK_RE.findall(post.content):
resolved = (path.parent / target).resolve() if not target.startswith("/") \
else (root / target.lstrip("/")).resolve()
if not resolved.exists(): # dangling: conformant, but rot
print(f"ERROR {path}: dangling link -> {target}"); errors += 1
print(f"{errors} error(s)")
return 1 if errors else 0
if __name__ == "__main__":
raise SystemExit(lint(Path(sys.argv[1])))import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const CANONICAL = new Set(["Service", "metric", "table", "runbook", "ADR"]);
const LINK_RE = /\[[^\]]*\]\(([^)#]+\.md)\)/g;
const root = path.resolve(process.argv[2]);
let errors = 0;
function walk(dir) {
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
e.isDirectory() ? walk(path.join(dir, e.name))
: e.name.endsWith(".md") ? [path.join(dir, e.name)] : []);
}
for (const file of walk(root)) {
const base = path.basename(file);
if (base === "index.md" || base === "log.md") continue;
try {
const { data, content } = matter(fs.readFileSync(file, "utf8"));
if (!data.type) { console.error(`ERROR ${file}: missing 'type'`); errors++; }
else if (!CANONICAL.has(data.type)) { console.error(`ERROR ${file}: non-canonical type '${data.type}'`); errors++; }
for (const m of content.matchAll(LINK_RE)) {
const target = m[1];
const resolved = target.startsWith("/")
? path.join(root, target) : path.resolve(path.dirname(file), target);
if (!fs.existsSync(resolved)) { console.error(`ERROR ${file}: dangling link -> ${target}`); errors++; }
}
} catch (exc) { console.error(`ERROR ${file}: unparseable (${exc.message})`); errors++; }
}
console.log(`${errors} error(s)`);
process.exit(errors ? 1 : 0);You built a lint that is deliberately stricter than the spec: canonical types only, no dangling links, hard failures. That inversion β lenient spec, strict pipeline β is the operating principle of every production OKF deployment. Your CI runs this before any agent is allowed to trust the bundle.
Hands-On Exercise: The orderflow Bundle
π Get the files: labs/M07-orderflow-bundle on GitHub β or clone the whole course once: git clone https://github.com/varasrinivas/knowledge-graph-course.git
What you'll build: the complete bundle in labs/M07-orderflow-bundle/ β root index.md, five cross-linked concepts (three services + the two M06 analytics concepts), and log.md β validated by the M06 lint. Time: 45β60 min.
- Step 1 β Author the three Service concepts (billing, orders, notifications) using the canonical shape above. Keep body sections identical across all three: Responsibilities, then Dependencies, then Citations.
β Checkpoint
Diff your three files' heading structure β it should be byte-identical.
- Step 2 β Write index.md. One line per concept: title + one-line description from frontmatter. If your index exceeds ~15 lines for 5 concepts, you're hoarding detail that belongs in the concepts.
- Step 3 β Cross-link both directions. billing-service links to notifications-service ("publishes to") AND notifications-service links back ("consumes from"). One-directional links are how bundles silently rot.
- Step 4 β Lint.
Expected:terminal
python ../M06-okf-authoring/solution/validate.py knowledge5 concepts valid, 0 errors.β CheckpointNow rename one linked file WITHOUT updating its inbound links and run your M07 dangling-link lint from the walkthrough. It must fail. Restore the file.
- Step 5 β Measure progressive disclosure. Count tokens (β chars/4) for index.md + one concept vs the whole
services/source tree. Record both numbers β the capstone reuses them.
Knowledge Check
1. What are the TWO substitutions that turn Google's table concept into a code concept?
2. Why is the bundle's link graph "richer than the filesystem tree"?
3. Your orchestrator is about to dispatch a sub-agent, and your CI is about to publish a bundle. Which okf primitive does each call?
4. What is an "attested computation" concept for?
5. Two teams write type: "API Endpoint" and type: "Route" for the same kind of concept. What does the spec do about it?
6. When the OKF link graph and the Graphify AST graph disagree about a dependency, what's the right reading?
Module Summary
What we built: orderflow's full narrative layer β three service concepts, two analytics concepts, a root index, bidirectional links, and a lint that's stricter than the spec.
Next module preview: a bundle that humans maintain by hand is a bundle that rots. M08 builds the enrichment pipeline β the git-hook-triggered, diff-scoped, two-pass agent process that keeps the graph honest while your team ships forty commits a day.
References
- OKF Specification v0.1 β github.com/GoogleCloudPlatform/knowledge-catalog
- okf CLI (Go, Apache-2.0) β github.com/superops-team/okf
- Kiso publishing engine β github.com/oak-invest/kiso
- Course corpus: "Standardizing Agent Memory: Building a Self-Updating Codebase Knowledge Graph with Google's OKF"; "Open Knowledge Format (OKF): A Complete Guide"