Skip to content
docs-bundle okf-loom wiki
GraphIndex

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

Reference 9 min read

okf-loom.config.yaml reference

Every `okf-loom.config.yaml` key — `bundle.*`, `viewer.*`, `search.*`, `validate.*`, `studio.*` — with type, default, and behaviour. Path-bearing fields are bundle-relative; unknown keys are preserved in `OkfConfig.raw`.

okf-loom.config.yaml reference

A bundle MAY ship an okf-loom.config.yaml at its root (current spec §5) to set viewer / search / validate / studio defaults. Absence ⇒ all defaults — the file is purely additive; nothing breaks without it. scripts/okf-loom init writes a commented-defaults template automatically; scripts/okf-loom bootstrap does not. See index.md for the rest of the reference quadrant.

The canonical filename is okf-loom.config.yaml (single constant in config.py; flip it to relocate).

Top-level shape

bundle:   { … }   # markdown scanning excludes
viewer:   { … }   # display + override settings
search:   { … }   # search-mode default
validate: { … }   # validation profile + broken-link policy
studio:   { … }   # live collaborative studio

The five top-level keys are the only recognised top-level keys. Unknown top-level keys are preserved verbatim in OkfConfig.raw for forward compatibility.

Path-bearing fields (SPEC §4.5 containment)

The following fields MUST be bundle-relative. Absolute paths and ..-escaping paths are rejected fail-closed at load time:

  • viewer.override_dir
  • viewer.extension_css
  • viewer.extension_js
  • studio.session_dir

The check reuses the SPEC §4.5 guard (log._resolve_within_bundle): it resolves the candidate against the bundle root and confirms containment via relative_to. Empty values are also rejected.

bundle.*

Controls which *.md files are part of the bundle when scanning (Bundle.load, the serve watcher, okf import). The scanner walks the tree top-down and PRUNES excluded directories, so serving or validating a real workspace root does not sweep node_modules, nested cloned repos, or virtualenvs in as thousands of missing-type pseudo-concepts — and never pays the cost of descending into them.

KeyTypeDefaultDescription
excludelist of strings (or one string)[]Gitignore-style patterns skipped during the scan. Sits above the defaults and .gitignore: a negation ("!.docs/") re-includes a path those groups excluded (git-style: not from under a pruned directory).
includelist of strings (or one string)[]The add-back lever. Positive patterns that beat EVERY exclusion source — defaults, .gitignore, exclude, and the structural nested-repo prune — and can reach inside pruned directories. Negations are rejected fail-closed.
respect_gitignorebooltrueHonour .gitignore files — the bundle root's AND nested ones, each scoped to its own directory — during the scan.

Three exclusion rule groups apply, later group wins, and within a group the last matching rule wins (git semantics):

  1. Built-in defaults: hidden directories (.git, .okf-loom, .venv, …), node_modules/, __pycache__/, venv/, bower_components/. Hidden files still load. Any non-root directory containing a .git entry (nested clone / submodule / worktree) is pruned — a structural rule, overridable only by include.
  2. .gitignore rules (when respect_gitignore is on).
  3. exclude patterns (this section).

include sits above all three. Two forms:

  • Directory form ("vendor-repo/", or a wildcard-free path like "node_modules/my-pkg/docs"): revives that directory — the scanner reaches it through pruned ancestors, then scans the subtree as normal bundle content. Exclusion rules that were overridden at the revived directory (e.g. an anchored node_modules/** in .gitignore) stay overridden beneath it; unrelated rules (hidden-dir default, basename rules like *.tmp.md, deeper excludes) keep applying inside. This is the recommended form.
  • Glob form ("vendor-repo/**"): force-includes exactly what it matches, everything beneath — defaults included. Broad ** on a big tree is a deliberate, paid-for traversal.

Include patterns without a / (basename form, "keep.md") apply wherever the scan already reaches but never open pruned directories — pulling something out of a pruned tree requires naming its path.

Pattern dialect (practical gitignore subset): * / ? / character classes never cross /; ** spans segments; trailing / = directory-only; a / anywhere else anchors the pattern to the bundle root (or the nested .gitignore's directory); leading ! = negation (exclude only); \ escapes; # comments.

exclude/include entries are match rules, not filesystem paths, so the §4.5 containment guard does not apply (a leading / is anchor syntax; matching is always against root-relative paths). Non-string entries are rejected fail-closed.

An unparseable config never makes the bundle unloadable: Bundle.load warns (config.unparseable) and scans with the built-in defaults.

bundle:
  exclude:
    - "drafts/**"                    # keep WIP notes out of validate/serve
    - "*.generated.md"               # tool output, not concepts
    - "!.docs/"                      # re-include a hidden dir the defaults pruned
  include:
    - "vendor-repo/"                 # nested git clone we DO want, scanned normally
    - "node_modules/my-pkg/docs/"    # one docs dir pulled out of node_modules
  respect_gitignore: true

Implementation: okf_loom/ignore.py (stdlib-only leaf module).

viewer.*

KeyTypeDefaultDescription
titlestring | nullnullViewer page <title>. null ⇒ no override; the viewer falls back to the bundle display name.
override_dirstring (bundle-relative path).okf-loom/viewerDirectory holding template / static / palette overrides.
extension_cssstring (bundle-relative path).okf-loom/viewer/extension.cssOptional CSS injected into every page.
extension_jsstring (bundle-relative path).okf-loom/viewer/extension.jsOptional JS injected into every page (subject to the active-code gate).
allow_active_codeboolfalseBundle-side gate for viewer overrides + plugins. The operator must ALSO consent via --allow-active-code / OKF_LOOM_ALLOW_ACTIVE_CODE (two-layer gate).

Boolean coercion

allow_active_code (and every other bool field) is coerced fail-closed. Accepted tokens (case-insensitive, whitespace-trimmed):

TrueFalse
true, yes, on, 1false, no, off, 0, ""

Any other value (e.g. "maybe", an int, a list) raises OkfConfigError. This is deliberately strict: a quoted "false" must NOT silently enable active code.

KeyTypeDefaultDescription
default_modeenumlexicalMode used when no --mode is passed. One of lexical, semantic, hybrid, tag, entity, relation.

Unknown values are rejected fail-closed (no silent fallback to a different backend). See search_modes.md.

validate.*

KeyTypeDefaultDescription
default_profileenumspecProfile used when no --profile is passed. One of spec, producer, loose.
fail_on_broken_linksboolfalsePromote link.broken findings to ERROR.
ProfileBehaviour
specDefault. Only type is required; everything else is WARNING.
producerspec + concept.missing_recommended_keys promoted to ERROR.
loosespec except concept.missing_type downgraded to WARNING.

Unknown default_profile values are rejected fail-closed. See cli.md § validate.

studio.*

Every default makes the studio fully functional with a plain scripts/okf-loom serve: live updates, commenting/directing, agent enrichment. No unlock flags required.

KeyTypeDefaultDescription
editbooltrueInteractive studio (commenting/directing + live). --no-edit flips to a read-only kiosk.
livebooltrueSSE live updates on.
enrich_offerbooltrueAgent offers to watch the bundle on bring-up.
auto_enrichbooltrueWhile watching, the agent applies edits directly.
constraintsmapping{}Empty = fully free agent. A user opts in restraints here (§12.3).
debounce_msint600Coalesce watcher-triggered work by this many ms.
session_dirstring (bundle-relative path).okf-loom/sessionWhere session state (events, directives, history) lives.
log_editsbooltrueAppend a SPEC §7 log.md entry per agent write / resolved comment.
max_sse_clientsint32Cap on concurrent SSE streams. Returns 503 when full.
allowed_hostslist of strings[127.0.0.1, localhost]Origin/Host allow-list for the cross-origin guard. Never empty (defaults to loopback on unset).
themeenumautoauto / light / dark.
events_max_bytesint8388608 (8 MiB)events.jsonl rotation cap (active file size).
events_keepint7Number of rotated events.jsonl files to keep.

allowed_hosts

Both Origin (when present) and Host headers are checked against this list (defense in depth against DNS-rebinding). The list is never empty: a missing/empty value defaults to ("127.0.0.1", "localhost"). A non-loopback bind requires scripts/okf-loom serve --public --public-ack (or interactive y/N ack).

constraints

An empty mapping means a fully free agent (the default; the agent edits the OKF files freely through the mutators). A user opts in restraints for their own agent here — e.g. propose_only: true or forbidden_paths: [...]. Constraints are never shipped; they are per-user, per-bundle policy. The mapping is preserved verbatim.

session_dir

Must be bundle-relative. An absolute path raises OkfConfigError; a ..-escaping path is rejected by the §4.5 guard. The directory holds events.jsonl, directives.jsonl, history/, .token.

Unknown keys

Unknown keys (top-level AND section-internal) are preserved verbatim in OkfConfig.raw. The same round-trip rule SPEC §2 applies to frontmatter applies here: a newer producer's config does not lose information when consumed by an older toolkit.

from okf_loom.config import OkfConfig

cfg = OkfConfig.load("path/to/bundle")
cfg.raw                  # dict of unknown top-level + section-internal keys
cfg.raw.get("acme_xxx")  # forward-compat read of an unknown key

Loading

SituationBehaviour
File absentAll defaults (OkfConfig()).
File empty / comment-onlyAll defaults (treated as empty mapping).
File unparseableOkfConfigError (subclass of ValueError).
Top level not a mappingOkfConfigError.
Path field escapes bundle rootOkfConfigError.
Unknown enum value (search.default_mode, validate.default_profile, studio.theme)OkfConfigError.
Alias/merge-key bombOkfConfigError (DoS guard via _NodeLimitedSafeLoader).

The loader uses the same node-counting YAML loader as frontmatter parsing, so the same DoS controls cover both surfaces.

Library

from okf_loom.config import OkfConfig, OkfConfigError

try:
    cfg = OkfConfig.load("path/to/bundle")
except OkfConfigError as e:
    ...  # unparseable / bad path / unknown enum

cfg.bundle.exclude              # () — gitignore-style patterns
cfg.bundle.include              # () — add-back patterns (beat every exclusion)
cfg.bundle.respect_gitignore    # True
cfg.viewer.title                # str | None
cfg.viewer.allow_active_code    # bool
cfg.search.default_mode         # "lexical"
cfg.validate.default_profile    # "spec"
cfg.validate.fail_on_broken_links  # False
cfg.studio.edit                 # True
cfg.studio.allowed_hosts        # ("127.0.0.1", "localhost")
cfg.studio.session_path         # ".okf-loom/session" (normalized)
cfg.raw                         # {"unknown_top": ..., "viewer": {"unknown_sub": ...}}

OkfConfig and its section dataclasses are frozen; construct a new one to change a value.

Relationship to other config surfaces

SurfaceScopeFormat
okf-loom.config.yamlBundle defaults (this doc)YAML at bundle root.
Bundle-root index.md frontmatterSPEC §6 / §11 declarations (okf_version, okf_extensions, generated)Frontmatter.
Concept frontmatterPer-concept metadataSPEC §4.1 (see frontmatter.md).
<bundle>/.okf-loom/viewer/config.jsonViewer-only display overrides (name, default_layout, theme)JSON.

okf-loom.config.yaml is the authoritative bundle-level config; the viewer's config.json is a narrower display-only override.

Authoring tools

GoalTool
Scaffold a new bundle with a commented-defaults configscripts/okf-loom init --bundle <path>.
Scaffold an empty bundle without a configscripts/okf-loom bootstrap <dest>.
Validate the configscripts/okf-loom validate <bundle> (the loader runs at bundle load; an unparseable config raises before any check).
Inspect resolved configOkfConfig.load(...) in Python.

See also

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