Skip to content

codekb Architecture

codekb is a separate Python package from sopkb - its own import root, its own console script, its own tests. Both ship inside the single kl4a PyPI distribution and share one OKF core.

Three packages, one core

flowchart TB
  subgraph codekb["codekb - the code option"]
    CK[pipeline · parse · relations<br/>mine · render · validate]
    CW[server · web · mcp]
  end
  subgraph sopkb["sopkb - the SOP option"]
    SK[normalize · mine · review<br/>export · web_app · mcp_server]
  end
  CORE[["kl4a<br/>models · bundle_store · ids · hashing<br/>okf_writer · llm_provider · llm_settings"]]
  CK --> CORE
  CW --> CORE
  SK --> CORE

KL4A is the product. sopkb and codekb are the two options it offers, and kl4a is the shared substrate they build on. Neither option depends on the other, so a code bundle never installs a document parser and a SOP bundle never installs a code parser.

What codekb takes from the substrate:

From kl4a Used for
bundle_store load_manifest, utc_now, relative_to_bundle
ids slugify
okf_writer OKFDocument, OKFDocumentError
hashing sha256_file - source checksums
llm_provider parse_author_response
llm_settings provider resolution for hybrid mining, via complete()

Why the substrate is a package of its own

It started inside the SOP package, which meant installing codekb pulled in the whole SOP toolkit — pdfplumber and python-docx included — for six modules' worth of shared code. Extracting it made codekb installable on its own. That substrate now lives in kl4a, alongside the kl4a --use TOOL ... dispatcher and the kl4a.sopkb/kl4a.codekb import-aliasing shims — but the substrate itself carries no dependency beyond PyYAML; the dispatcher and aliasing layer add none of their own.

The rule that keeps it honest: nothing profile-specific goes in the substrate. Document parsing belongs to sopkb, code parsing to codekb, and tools/kl4a/tests/test_core_stands_alone.py fails if either creeps back in.

Module map

Module Responsibility
model.py CodeBundle — reads .codekb/code_*.json and indexes it for lookup
state.py Atomic state and manifest writes, and tolerant reads
config.py Bundle config and the standard bundle location
ids.py Stable identifiers for modules, symbols, evidence, relations
bundle.py Create a bundle; write state and Markdown
inventory.py Scan the repository into a source inventory
parse.py Source files into modules and symbols with evidence
relations.py Edges between symbols, and their resolution status
architecture.py, entrypoints.py Framework detection
knowledge.py, author.py Mining, static and LLM-assisted
cache.py Enrichment cache for hybrid runs
lifecycle.py Claim lifecycle across re-mines
render.py The human-readable Markdown layer
validate.py Bundle validation
context.py Task-ready code context — the codekb context command reads through here
agent.py The deterministic code-agent harness behind codekb agent
canonical.py, trace.py, trace_validate.py, transform.py Cross-bundle migration
pipeline.py, run_state.py End-to-end build, foreground and background
adapters/ Per-language adapters (COBOL today)
mcp.py The read-only code.* MCP surface
cli.py codekb

Concurrency: why codekb writes state itself

The pipeline can write state while other consumers read the same .codekb/code_*.json files and manifest.yaml. A plain truncate-then-write lets a reader observe an empty or partial document.

state.py writes to a sibling temp file and os.replaces it, so a reader sees either the previous version or the new one, never a torn one. On Windows the replace fails with PermissionError while another process holds the destination open for reading, so it retries briefly; the write itself stays atomic. Reads are also tolerant — an empty or corrupt file reads as "no state yet" rather than raising.

This is why codekb does not simply call kl4a.bundle_store.write_json or .save_manifest. codekb owns the background worker, so it owns the guarantee. save_manifest is the more consequential of the two: codekb rewrites manifest.yaml twice per run while the pipeline page re-renders every few seconds, and the readers that load it swallow a parse error and fall back to {}, so a torn manifest does not fail loudly — it silently renders a hybrid bundle as static with a blank repository path. state.save_manifest writes the same bytes as kl4a's, through write_text_atomic.

What lands on disk

my-code-bundle/
  manifest.yaml          profile: code-knowledge-bundle
  .codekb/                pipeline state, one file per stage
  code/                  rendered module and symbol pages
  reports/               validation, coverage, relation resolution
  overview.md            the human entry point

Each stage writes its own state file, so a run that fails partway leaves everything the earlier stages produced. See Bundle State.

Where the boundary sits

  • A Code Knowledge Bundle is an OKF bundle with profile: code-knowledge-bundle. The bundle model — sources, sections, knowledge items, evidence, relations — is shared with document bundles and specified in the OKF Bundle Spec.
  • LLM provider settings are read from the same saved settings as sopkb, so configuring a provider once covers both.