Skip to content
docs-bundle okf-loom wiki
GraphIndex

Live studio unavailable. Showing a read-only view. Browse the index.

Reference 19 min read

Embedding Guide

How to wire okf-loom into any agent / meta-harness — CLI shell-out, Python library, and long-running server shapes, with harness-specific recipes for opencode, Claude Code, Codex, LangGraph, MCP, and custom harnesses.

/reference/embedding_guide.md embeddingharnessintegrationopencodeclaude-codecodexlanggraph

Embedding okf-loom in an Agent Harness

This guide shows how to wire okf-loom into any agent / meta-harness. okf-loom is harness-agnostic by design: no agent-framework code lives in this repository. The intended audience is agents in opencode / Claude Code / Codex that point at the okf CLI — so Shape 1 (CLI subprocess) is the primary path. Shape 2 (Python library import) and Shape 3 (embedded HTTP server) are advanced seams for harness authors who need them. Shape 4 covers the live studio agent loop (built on shapes 1–3).

Pick the shape that matches how your harness already works:

ShapeWhen to useProsCons
1. CLI subprocess (primary)Your harness already shells out to CLIs (most do — including opencode, Claude Code, Codex)Zero coupling; JSON I/O; language-agnosticProcess-spawn latency per call
2. Python library (advanced)Your harness is Python and you want in-process caching / the full API without subprocess overheadNo subprocess; in-process caching; full APICouples to Python
3. Embedded serverYou want a long-running wiki the harness can point a browser atInteractive; hot-reload; same process as the agentOne server per bundle
4. Live studio & agent loopYou want the collaborative studio: user directs via comments, agent authors the bundle liveReal-time collaboration; headless scripts/okf-loom watch feed; in-process event busOne server per bundle; agent must watch + write via mutators

Read SKILL.md first if you are an agent — it is the loadable skill entrypoint. Focused skill resources live under resources/.


Shape 1 — CLI subprocess

The simplest integration. Your agent constructs a command, runs it, and parses JSON on stdout. Most harnesses already do this for git, rg, npm, etc.

OKF_REPO=/path/to/okf-loom

# Validate (returns 0 ok / 1 conformance fail / 2 strict fail)
"$OKF_REPO/scripts/okf-loom" validate /path/to/bundle --format json

# Inspect
"$OKF_REPO/scripts/okf-loom" info /path/to/bundle --format json

# Find gaps (writes a JSON report)
"$OKF_REPO/scripts/okf-loom" discover /path/to/bundle --out /tmp/plan.json

# Apply an update plan (idempotent)
"$OKF_REPO/scripts/okf-loom" update /path/to/bundle --plan /tmp/plan.json --format json

# Stand up the viewer (blocks; the harness can run it in a side-thread or background process)
"$OKF_REPO/scripts/okf-loom" serve /path/to/bundle --port 8787 --no-open

Report commands document their JSON mode in $OKF_REPO/scripts/okf-loom <cmd> --help; server/watch/render/build commands expose command-specific output flags instead. This shape is language-agnostic — a Node, Go, or Rust agent can use okf-loom identically.

Discovery → Update loop (the typical agent task)

# 1. Find what's missing
scripts/okf-loom discover /path/to/bundle --format json > /tmp/discovery.json
# (the agent reads discovery.json, decides which suggestions to accept,
#  and writes /tmp/plan.json with the matching UpdateOp entries — see
#  docs-bundle/reference/cli.md and resources/advanced-operations.md for the op-kind reference)

# 2. Preview
scripts/okf-loom update /path/to/bundle --plan /tmp/plan.json --dry-run

# 3. Apply
scripts/okf-loom update /path/to/bundle --plan /tmp/plan.json

# 4. Re-validate
scripts/okf-loom validate /path/to/bundle --format json

Shape 2 — Python library (advanced)

This is an advanced seam for harness authors building a dedicated Python integration that wants in-process caching or the full library API without subprocess overhead. The primary embedding path for agents in opencode / Claude Code / Codex is Shape 1 (CLI subprocess) above — those agents use the okf CLI, they do not import okf_loom.

For Python harnesses (LangGraph tools, custom agents) that need it.

from okf_loom import Bundle
from okf_loom.validate import validate_bundle
from okf_loom.discover import discover_suggestions
from okf_loom.update import load_plan, apply_plan
from okf_loom.search import search_bundle, SearchMode

bundle = Bundle.load("/path/to/bundle")

# Validate
report = validate_bundle(bundle)
if not report.ok:
    for f in report.errors:
        print(f.path, f.code, f.message)

# Discover gaps
discovery = discover_suggestions(bundle, rules=["unlinked_mentions", "orphan_concepts"])
for s in discovery.suggestions:
    print(s.rule, s.concept_id, s.action, s.message)

# (your code converts accepted suggestions to UpdateOp entries and
#  writes a plan file — see docs-bundle/reference/cli.md and resources/advanced-operations.md)

# Apply a plan
plan = load_plan("/tmp/plan.json")
summary = apply_plan(bundle, plan)
print(summary["applied"], "applied;", summary["skipped"], "skipped")

# Search
for r in search_bundle(bundle, "customer order", mode=SearchMode.LEXICAL, limit=5):
    print(r.score, r.concept_id, r.title)

Cache invalidation. Bundle.graph() and Bundle.content_index() memoize on the instance. After mutating the bundle via apply_plan, reload: bundle = Bundle.load(bundle.root). The lexical search backend caches on the Bundle instance itself; long-running servers that reload bundles evict it by reloading the bundle or calling bundle.invalidate(). okf_loom.search.clear_search_cache() is a deprecated no-op kept for import compatibility.


Shape 3 — Embedded HTTP server

Run the live viewer inside the agent process so a browser (the user, a scraper, an MCP browser tool) can navigate the bundle while the agent works on it.

import threading
from http.server import ThreadingHTTPServer
from okf_loom import Bundle
from okf_loom.server import OKFWikiHandler

bundle = Bundle.load("/path/to/bundle")
httpd = ThreadingHTTPServer(("127.0.0.1", 8787), OKFWikiHandler)
httpd.bundle = bundle                       # the handler reads this attribute
threading.Thread(target=httpd.serve_forever, daemon=True).start()

# Later, after the agent has mutated the bundle on disk:
httpd.bundle = Bundle.load("/path/to/bundle")  # hot-swap; next request sees it

Routes are documented in HTTP routes and advanced operations. The most useful for agents:

  • /<concept_id> — rendered concept page.
  • /__raw/<concept_id> — raw markdown body (for the agent to read into its own context).
  • /__data/graph.json — the link graph as JSON (for the agent to reason about structure).
  • /__data/content.json — the full content index as JSON.
  • /__search?q=…&format=json — search-as-you-type results.

Shape 4 — Live studio & the agent loop

scripts/okf-loom serve runs a live collaborative studio: the user directs (reads + comments) and an external agent authors the OKF files in real time. The server stays LLM-free — it emits events and runs mechanical ops; the agent (in opencode / Claude Code / Codex / any harness) does the thinking and writes back through the existing mutators. This shape is how an agent harness wires into that loop. See /reference/spec.md §10 for the posture and the bring-up "watch" question.

Bring-up (§3). When you open the studio for the user, ask one courtesy question: "I've opened the live studio for <bundle>. Want me to watch it and keep it enriched — proactively adding links, entities, relations, indexes, and descriptions as it changes? Either way I'll do whatever you ask via comments." This is a collaboration choice, not a permission gate — the agent is free to edit the OKF files regardless (current spec §10).

4a — Headless feed with scripts/okf-loom watch (continuous tail)

scripts/okf-loom watch reuses the bundle watcher and prints one event per line (the §7.2 schema) for a long-running consumer to pipe to the agent. No HTTP server needed.

# Tail every change to the bundle as JSON Lines
scripts/okf-loom watch /path/to/bundle --emit jsonl

# Reconnect-safe: replay missed events from an id, then tail live
scripts/okf-loom watch /path/to/bundle --emit jsonl --since 01JABED5K7

# Let okf-loom run mechanical repairs on change (debounced), debounced 600ms
scripts/okf-loom watch /path/to/bundle --auto-repair --debounce-ms 600

serve and watch both append the durable, replayable <bundle>/.okf-loom/session/events.jsonl — the audit trail and how a headless agent "sees" the user's saves.

4a′ — Foreground block-once with scripts/okf-loom wait (the primary agent loop)

When the agent itself is the consumer (the common case in opencode / Claude Code / Codex), prefer scripts/okf-loom wait over scripts/okf-loom watch. It blocks until there is work (a new open comment and/or a change event), prints that one item as JSON, and exits — so the agent wakes, acts, and re-waits in a clean foreground loop:

# Blocks until a new OPEN user comment, prints it, exits 0. AWAIT this —
# do NOT background it (you are the processor; a backgrounded wait has no
# consumer, so comments would sit open forever).
scripts/okf-loom wait /path/to/bundle --for comment

# Wait for comments OR proactive-enrichment changes (whichever first):
scripts/okf-loom wait /path/to/bundle --for comment change --timeout 600

Library equivalent: okf_loom.studio.wait_for_work(bundle_root, kinds=..., since=..., timeout=..., interval=...). The loop is: scripts/okf-loom wait → read JSON → reason → edit via mutators (scripts/okf-loom link-add / update / entity-add / repair) → resolve the comment → scripts/okf-loom wait again.

4b — The comment → claim → write → resolve flow

The user's only studio writes are comments / asks (POST /__comment), which land in <bundle>/.okf-loom/session/directives.jsonl. The agent's loop (run scripts/okf-loom wait in your foreground — you are the processor):

# 0. Studio must be up so the session dir + .token exist.
scripts/okf-loom serve /path/to/bundle --no-open &

# 1. Block-once for the next OPEN comment.
scripts/okf-loom wait /path/to/bundle --for comment      # → {id, concept, anchor, body, …}

# 2. Claim it + announce presence.
scripts/okf-loom comment-claim /path/to/bundle 01JABED9K0
scripts/okf-loom presence     /path/to/bundle --state editing --focus tables/orders

# 3. Author via the mutators. With a live session the CLI routes through
#    Studio.save_concept — each op is attributed (actor=agent, origin=mutator),
#    undoable, and emits 'changed' + 'graph'. --group-id groups the pass for
#    one-click group undo (§12.5).
scripts/okf-loom plan     /path/to/bundle --scope tables/orders --neighbors --out /tmp/p.json
scripts/okf-loom link-add --bundle /path/to/bundle --source tables/orders \
             --target tables/customers --relation depends_on --group-id PASS1
scripts/okf-loom entity-add --bundle /path/to/bundle --id tables/orders \
               --label "Order" --kind Table --group-id PASS1
scripts/okf-loom update   /path/to/bundle --plan /tmp/p.json --group-id PASS1

# 4. Resolve the comment, linking the activity ids the writes produced.
scripts/okf-loom comment-resolve /path/to/bundle 01JABED9K0 \
    --activity 01JABED5K7,01JABED5K8 --reply "Done - added depends_on + Order entity"

# 5. Loop: scripts/okf-loom wait again.

Each write flows file → Studio.save_concept → watcher → live update (SSE) + a change-list entry + an undo snapshot — the user sees it appear in place, with a highlight, no full refresh.

The per-session CSRF token (X-OKF-Token) and Origin/Host allow-list guard every mutating endpoint (current spec §14). The CLI mutators and the comment-* / presence / token commands attach the token for you. If you must POST to /__apply / /__undo / /__presence / /__comment over HTTP directly, read the token via scripts/okf-loom token /path/to/bundle (it reads <bundle>/.okf-loom/session/.token, mode 0600, set on scripts/okf-loom serve boot) — never hand-copy it:

TOKEN=$(scripts/okf-loom token /path/to/bundle)
curl -s -X POST http://127.0.0.1:8787/__presence \
     -H 'Content-Type: application/json' \
     -H "X-OKF-Token: $TOKEN" \
     -d '{"actor":"agent","state":"editing","focus":"tables/orders"}'

For a fully headless integration (no scripts/okf-loom serve running), the CLI mutators fall back to the non-studio atomic write path: same data-safety, but no attribution / undo / live push. To get the live collaboration surface, run scripts/okf-loom serve (or any process that creates .okf-loom/session/) before invoking the mutators.

4c — Embedding the studio in-process (Python)

For a Python harness that wants the studio, the event bus, and the watcher in the same process (no subprocess, no HTTP round-trip):

import threading, queue
from okf_loom.studio import Studio
from okf_loom.watch import run_watch

# 1. Construct (or reuse) the studio handle for a bundle.
studio = Studio.for_bundle(
    "/path/to/bundle",
    session_rel=".okf-loom/session",   # default
    log_edits=True,               # append a SPEC §7 log.md entry per write
    bundle_name="My Bundle",
    max_queue=64,
)
studio.ensure_session()

# 2. Subscribe to in-process events (returns a bounded queue.Queue you poll).
bus_q: queue.Queue = studio.bus.subscribe()
def _drain():
    while True:
        event = bus_q.get()        # blocks until an event arrives
        print("studio event:", event)   # {type, rev, ids, actor, ...} (§7.2)
threading.Thread(target=_drain, daemon=True).start()

# 3. Publish from your own code if you drive the bundle programmatically:
studio.bus.publish({"type": "presence", "actor": "agent", "state": "watching"})

# 4. Headless feed as a library call (same loop `scripts/okf-loom watch` runs):
#    run_watch("/path/to/bundle", emit="jsonl", since=None,
#              auto_repair=False, debounce_ms=600)  # -> exit code (blocks)

run_server(..., studio_edit=True, studio_live=True) (see Shape 3) attaches server.studio — the Studio instance (or None if both are off) — so a daemon-thread embedder can reach httpd.studio.bus.subscribe() for events without launching scripts/okf-loom watch separately.

EventBus.subscribe(callback) — the in-process fan-out shape (§17)

subscribe() also takes an optional callback for the simplest in-process embedding — no queue to poll, no thread to spawn:

from okf_loom.studio import Studio

studio = Studio.for_bundle("/path/to/bundle")
studio.ensure_session()

# One subscription = one callback invoked per published event from any writer
# (the CLI mutators in this process, the watcher's disk-backfill, your own
# publish() calls). Returns an opaque sub handle you pass to unsubscribe().
sub = studio.bus.subscribe(callback=lambda ev: print("event:", ev))

# ... later, when you no longer care:
studio.bus.unsubscribe(sub)

The callback runs on the publisher's thread, so keep it cheap (hand off to a worker queue if you do heavy work). The bus delivers events in publish order; a slow callback does not block other subscribers (they each have their own bounded queue, and a callback subscriber that lags gets a resync event).

EventBus.subscribe(callback) trust boundary (P3-12 / SEC2-007)

subscribe(callback) runs the caller-supplied Python callable inside the server process. This is intentional — it is the §17 in-process embedding seam — but it is also a trust boundary equivalent to the bundle active-code gate (§15.4):

  • Bundle active-code (the {{ ... }}/{% ... %} template blocks a bundle can embed) is sandboxed to the bundle directory and gated by the operator-consent flag. A bundle cannot reach EventBus.subscribe from inside the server — the bundle has no Python import surface, only rendered markdown.
  • EventBus.subscribe(callback) is the harness-author surface. A callback you register has the full privilege of the server process: it can import, open(), subprocess.run, read <bundle>/.okf-loom/session/.token, and write to the bundle. The CSRF token, Origin/Host allow-list, and --public ack gate that protect the HTTP surface do not constrain a callback registered through this seam — they guard the network boundary, and the callback is in-process.

Practical implications:

  1. A malicious harness extension can execute arbitrary Python in the server process. Treat any code that calls studio.bus.subscribe(cb) the same way you would treat code that calls eval() on the server: only register callbacks from code you trust. okf-loom does not sandbox the callback for you.
  2. When embedding in a larger harness (opencode / Claude Code / Codex / LangGraph / a custom agent), do not forward untrusted network input directly into a callback. If you need to expose bus events to an untrusted consumer, use the queue form (bus.subscribe() with no callback) and serialize events over a boundary you control (a pipe, a file, an HTTP endpoint with its own auth).
  3. Sandbox if possible. If you have a reason to register an untrusted callback (e.g. a user-supplied plugin), run it in a subprocess or a multiprocessing-isolated worker and proxy events to it. Do not rely on Python's "no real sandbox" exec restrictions.
  4. Exception behavior. The worker thread that invokes your callback swallows exceptions by design — a buggy callback must never kill the event bus (§13.8 robustness). Swallowed exceptions are logged to stderr with the prefix [okf-callback] so they are not silent. Redirect the server process's stderr to your log collector to capture them:
   scripts/okf-loom serve /path/to/bundle 2>>/var/log/okf-callbacks.log

If you want exceptions to surface more aggressively (e.g. fail a test suite), wrap your callback:

   _seen = []
   def safe_cb(event):
       try:
           my_logic(event)
       except Exception as e:
           _seen.append(e)   # assert on _seen in your test

   sub = studio.bus.subscribe(callback=safe_cb)
   # ... after the test:
   assert not _seen, f"callback raised: {_seen}"

The single-source guarantee is: the bundle cannot register a callback (it has no Python surface in the server); only the harness that owns the server process can. If you trust the harness, you trust its callbacks.

The §17 library API (all names implemented)

The HTTP layer is a thin wrapper over these — one implementation, whoever the caller is. Use them when you embed the studio in-process instead of shelling out to the CLI:

MethodHTTP equivalentPurpose
studio.save_concept(*, concept_id, raw, actor="agent", action="write_concept", origin="mutator", group_id=None, expected_rev=None, summary=None, detail=None, undoable=True, emit_graph=None, path=None, publish=True) -> WriteResultPOST /__apply (per-op)The single internal atomic write path. Validates before write, snapshots for undo, atomically writes, appends a row to events.jsonl, broadcasts changed (+ graph for graph-affecting ops). Raises / returns a conflict on expected_rev mismatch (§9.3/§9.4).
studio.post_presence(*, actor="agent", state="idle", focus=None) -> dictPOST /__presenceSet agent presence (current spec §12). State ∈ idle/watching/thinking/editing. Broadcasts a presence event.
studio.post_comment(*, concept, body, anchor=None, actor="user", detail=None) -> dictPOST /__commentAppend a user comment / ask to directives.jsonl (§9). Returns the new directive.
studio.resolve_comment(comment_id, *, reply=None, activity_ids=None, actor="agent") -> dict | None(CLI: scripts/okf-loom comment-resolve)Mark a comment resolved with optional reply + activity links (drives group undo, §12.5).
studio.record_activity(*, actor, action, ids, summary, origin="mutator", undoable=False, group_id=None, detail=None, emit_graph=None) -> dict(SSE activity event)Append an activity event row to events.jsonl and broadcast it. Use for actions that don't write a concept file but should still show on the change list.
studio.events_append(event, *, publish=True) -> dict(writer helper)The low-level append every other method uses. Adds the id/ts/seq, takes the advisory file lock, writes one JSON line to events.jsonl, optionally broadcasts. Use this only if you need a fully custom event.
studio.post_suggestion(*, concept, action, args, summary, group_id=None, actor="agent") -> dict(SSE suggestions event)Only under a user's opt-in propose_only constraint (§12.3). Appends a PlannedAction envelope to proposals.jsonl instead of writing. Absent by default.

Plus studio.list_comments(...), studio.get_comment(id), studio.update_comment(id, …), studio.set_presence(...), studio.get_presence(), studio.read_events(since=None, limit=None, actor=None, concept=None), studio.snapshot_for_undo(...), studio.restore_snapshot(...), studio.undo_group(group_id), studio.mark_logged(id), studio.current_rev(cid), and the module-level wait_for_work(bundle_root, *, kinds=..., since=..., timeout=..., interval=...) (the same loop scripts/okf-loom wait runs).

Client-side extension: window.okfLoomStudio.register(kind, config)

The browser studio exposes an additive, CSP-safe JavaScript extension seam (current spec §15) mirroring the server ViewerPlugin intent but running fully client-side after the page boots. panel (add a slide-over panel + studio-bar toggle; the built-in Comments / Changes / Agent activity panels use it) and viewMode (add a button to the Rendered/Source/Split switch) ship wired; toolbar, graphDecorator, and suggestionRenderer are reserved (accepted + console.warn, not wired). Load a register(...) extension from a .okf-loom/viewer/static/*.js override or eval it from your harness after the studio boots. See scripts/okf_loom/viewer/OVERRIDES.md §6 for the full API and worked panel/view-mode examples.

The bundle-on-disk is the bus

The studio is not the source of truth — the .md files are. The studio is the fan-out + attribution + undo layer over them. Every write goes:

your code ──▶ studio.save_concept ──▶ atomic write of <bundle>/<id>.md
                                       │
                                       ▼
                            .okf-loom/session/events.jsonl  (one row per write)
                                       │
                            ┌──────────┴───────────┐
                            ▼                      ▼
                    EventBus.publish        _BundleWatcher detects
                            │                mtime change, diffs the
                            ▼                ContentIndex, and (if the
                    SSE /__events           write didn't log itself)
                    fan-out to every         appends an origin:"disk"
                    open tab + every         event — so the feed is
                    in-process               complete regardless of
                    subscriber                which path produced it.

For an embedded harness this means: writes via save_concept (or the CLI mutators) → events.jsonl → SSE + EventBus fan-out, all from the same files a scripts/okf-loom watch would tail. A second harness process embedding the same bundle sees the same events through the file bus — no IPC beyond the filesystem. This is what keeps the studio headless-friendly (§1.9 of the current spec) and lets the agent run externally while the studio runs in-process for the user's browser.

4d — What to expose for the agent loop

At minimum, expose for the studio loop:

  1. scripts/okf-loom serve <bundle> --no-open — bring up the live studio (hand the user the URL).
  2. scripts/okf-loom watch <bundle> --emit jsonl [--auto-repair] — the headless change feed that drives proactive enrichment (§11).
  3. The mutators (scripts/okf-loom link-add / entity-add / update / repair / write-concept) — how the agent actually authors files in response to comments and the watch feed.
  4. POST /__presence (or a write to .okf-loom/session/presence.json) — so the user can see what their teammate is doing.

Harness-specific recipes

opencode

opencode discovers project-scoped agents and skills under .opencode/ and an AGENTS.md at the repo root. To wire okf-loom into an opencode project:

  1. Clone or vendor this repository into the agent's workspace. The repo skill layout is the artifact; do not depend on package installation.
  2. Expose the root skill under .opencode/skill/okf-loom/ (or keep the vendored checkout there). The skill directory must include SKILL.md, resources/, scripts/, and docs-bundle/ together:
   .opencode/skill/okf-loom/
   ├── SKILL.md
   ├── resources/
   ├── scripts/
   └── docs-bundle/

opencode's skill loader will surface it; the resolver matches on the description triggers (e.g. "okf", "validate okf", "knowledge bundle").

  1. Copy or include AGENTS.md from this repository to your project root only if your harness starts from AGENTS files. It points back to SKILL.md.
  2. Scope a subagent for OKF work (.opencode/agent/okf-curator.md):
   ---
   description: Curates, validates, and updates OKF bundles. Use for
     "okf", "validate knowledge", "discover missing links", "update
     okf bundle", "stand up okf viewer".
   mode: subagent
   tools:
     bash: true
     read: true
     write: true
     edit: true
   ---
   You are the OKF curator. Always load `okf-loom/SKILL.md` first,
   then read the focused resource for the task (`resources/format-basics.md`,
   `resources/authoring.md`, `resources/advanced-operations.md`, or
   `resources/studio-agent-loop.md`). Use the checked-in helper:
   `/path/to/okf-loom/scripts/okf-loom`.
   Run `scripts/okf-loom validate` from inside the checkout, or `okf-loom/scripts/okf-loom validate` from a parent workspace, before claiming completion; run `discover` to find
   gaps; apply suggestions via `update --plan`. Never bulk-convert existing
   content without user confirmation.
  1. Permission scoping (opencode permission.bash patterns):
   {
     "permission": {
       "bash": {
         "scripts/okf-loom validate *": "allow",
          "scripts/okf-loom info *": "allow",
          "scripts/okf-loom discover *": "allow",
          "scripts/okf-loom graph *": "allow",
          "scripts/okf-loom search *": "allow",
          "scripts/okf-loom capabilities *": "allow",
          "scripts/okf-loom render *": "allow",
          "scripts/okf-loom build *": "allow",
          "scripts/okf-loom index * --dry-run": "allow",
          "scripts/okf-loom index *": "ask",
          "scripts/okf-loom log * --dry-run": "allow",
          "scripts/okf-loom log *": "ask",
         "scripts/okf-loom update * --dry-run": "allow",
         "scripts/okf-loom update *": "ask",
         "scripts/okf-loom serve *": "ask"
       }
     }
   }

Claude Code

Claude Code reads CLAUDE.md at the repo root and skills under .claude/skills/. The shape mirrors opencode:

  1. Copy or symlink root SKILL.md into the harness skill directory.
  2. Copy the resources/, scripts/, and docs-bundle/ folders alongside it.
  3. Claude Code's skill/resource discovery will surface the skill when the user mentions OKF / "knowledge bundle" / "validate okf" etc.

Codex (OpenAI)

Codex reads AGENTS.md at the repo root (the same file opencode uses). Skill conventions vary; the simplest path:

  1. AGENTS.md at repo root (already in this repository; copy verbatim).
  2. Reference SKILL.md from AGENTS.md so Codex reads the root skill and resources when relevant. If auto-discovery is unavailable, an explicit load okf-loom/SKILL.md when the user asks about OKF line works well.

LangGraph

Wrap the CLI in tool functions:

from langchain_core.tools import tool
import subprocess, json, pathlib

def _okf(*args):
    # The repo ships no installed console script; shell out to the checkout path.
    r = subprocess.run(["okf-loom/scripts/okf-loom", *args], capture_output=True, text=True, check=True)
    return r.stdout

@tool
def okf_validate(bundle_path: str) -> dict:
    """Validate an OKF bundle. Returns conformance report JSON."""
    return json.loads(_okf("validate", bundle_path, "--format", "json"))

@tool
def okf_discover(bundle_path: str) -> dict:
    """Find missing links/indexes/descriptions/relations/orphans in an OKF bundle."""
    return json.loads(_okf("discover", bundle_path, "--format", "json"))

@tool
def okf_update(bundle_path: str, plan_path: str, dry_run: bool = False) -> dict:
    """Apply an OKF update plan (add link/tag/relation/section). Idempotent."""
    args = ["update", bundle_path, "--plan", plan_path, "--format", "json"]
    if dry_run:
        args.append("--dry-run")
    return json.loads(_okf(*args))

@tool
def okf_search(bundle_path: str, query: str, mode: str = "lexical", limit: int = 10) -> list:
    """Search an OKF bundle. mode ∈ {lexical, semantic, hybrid, tag}."""
    return json.loads(_okf("search", bundle_path, query, "--mode", mode,
                           "--limit", str(limit), "--format", "json"))

Wire these into your graph's tools list. The agent will reach for them when the user mentions knowledge / docs / catalogs.

MCP server (model context protocol)

The CLI is shaped for an MCP wrapper. A minimal MCP server exposes okf_validate, okf_discover, okf_update, okf_search, okf_render as tools. The --format json outputs are already JSON-serialisable. (There is no in-repo MCP server today; wrap the CLI as shown.)

Custom agent (any language)

For a non-Python agent, the CLI subprocess shape works from any language. Construct the argv, run, parse --format json stdout. Treat okf-loom like git or rg: a standard Unix CLI.


What to expose to your agent

At minimum, expose these five commands as agent-callable tools (the "trigger list" the agent uses to decide when OKF is relevant):

  1. scripts/okf-loom validate — for any "validate / check / lint" intent.
  2. scripts/okf-loom discover — for any "what's missing / find gaps / improve" intent.
  3. scripts/okf-loom update — for applying the agent's proposed fixes.
  4. scripts/okf-loom search — for any "find / show me / where is" intent over the knowledge.
  5. scripts/okf-loom serve — for any "show me / browse / open the wiki" intent.

The agent note (AGENTS.md) is the routing layer — it tells the agent WHEN to reach for OKF (the trigger list) and points to the skills for HOW.

Testing your embedding

Whatever shape you pick, verify:

  1. Round-trip safety. Apply a no-op update plan to a bundle; the bundle's content should be unchanged (modulo YAML whitespace).
  2. Idempotency. Apply the same plan twice; the second run reports applied:0, skipped:N.
  3. Strict mode in CI. scripts/okf-loom validate --strict should fail the build on warnings.
  4. Hot-swap. In Shape 3, mutate the bundle on disk, swap httpd.bundle, hit / again; the new content should appear.

The samples/demo_bundle/ or samples/showcase/ directories are good test corpora.

Reference

All fields
timestamp2026-06-29T00:00:00Z