Skip to main content
Entity extensions add schemas. Developer registries add physical behavior. Both plug in without changing the workspace API.

1. Two Extension Layers

HeavenBase separates extension work into two layers that share the hb.ext import path but serve different authors:
LayerAudienceWhat it addsPrimary API
Entity extensionApplication and package authorsOptional Entity classes and MetaSchema rowsExtensionSpec, register_extension, ws.enable_extension
Developer extensionBackend and compiler authorsBackends, handlers, strategies, logical types, opshb.ext.register_backend_builder, register_strategy, register_handler, …
Entity extensions publish structure through normal workspace registration. Developer extensions publish behavior through process-global registries that the workspace planner and handler seeding consume at runtime.

2. Built-In System and Prompt Extensions

HeavenBase ships one required built-in entity extension: system. Every HeavenBase(...) workspace enables it automatically, and default configuration also loads prompt. The system implementation lives under src/heavenbase/extensions/system/; Prompt and Translation live under src/heavenbase/extensions/prompt/. Import hb.Prompt, hb.Capsule, and hb.Toolkit from import heavenbase as hb.

2.1. Built-In Entities

Entity idClassPurpose
sys-catalogCatalogConcrete object discovery
sys-metaschemaMetaSchemaStructure, capabilities, extensions
sys-promptPromptCallable prompt rows from the prompt extension
sys-translationTranslationPrompt-bound translations from the prompt extension
sys-capsuleCapsuleExecutable Capsule manifests
sys-toolkitToolkitToolkit manifests
ConfigLayer (sys-config-layer) exists in the same package but is not part of system_entities() and is not auto-enabled.

2.2. What Changed in the Architecture Optimization Pass

Capsule and Toolkit remain under the required system extension; Prompt and Translation live in the default-loaded prompt extension with the same root package API. Lazy helpers such as ensure_prompt_entities(ws) call ws.enable_extension("prompt"); Capsule registry setup calls ws.enable_extension("system") when it needs system rows.

3. Entity Extension API

Define and register entity extensions before workspaces load them:
import heavenbase as hb
from heavenbase.ext import Extension, ExtensionSpec, register_extension


class AuditLog(hb.Entity):
    identifier = "audit-log"

    event = hb.field(hb.ShortText)


register_extension(
    Extension(
        ExtensionSpec(
            identifier="audit",
            name="Audit Log",
            desc="Optional audit rows.",
            entities=(AuditLog,),
            tags=("audit",),
        )
    )
)
ExtensionSpec fields:
FieldRole
identifierStable extension id (same naming rules as workspace ids)
name, desc, versionHuman-readable metadata published to MetaSchema
entitiesEntity classes registered when the extension is enabled
requiredWhen True, the extension cannot be skipped (only system uses this today)
tags, metaExtra metadata for discovery and tooling
Enable an extension in one workspace:
ws = hb.HeavenBase("demo", preset="debug")
spec = ws.enable_extension("audit")
print(spec.identifier)
print(ws.extensions())
Enable custom extensions on every new workspace through config:
# heavenbase.extensions.default: ["audit", "my-package"]
Entity extensions do not bypass storage planning. Enabled entities still go through ws.register(...) and normal routing.

4. Developer Extension Registries

Developer extensions register physical behavior in process-global registries exported from hb.ext:
RegistryRegister functionUsed by
Backendsregister_backend_builder, register_backend_classConfig loading, storage placement, handler seeding
Handlersregister_handler, HandlerRegistry.registerQuery compilation
Strategiesregister_strategyStorage placement and handler lookup
Logical typesregister_logical_typeHandler seeding and storage profiles
Operationsregister_opJSON query parsing and handler seeding
Storage profilesregister_storage_profileDefault field placement
Handlers compile one logical operation into one QueryFragment. They do not execute IO; backends execute fragments.
import heavenbase as hb

def compile_ends_with(field_schema, value, ctx):
    def payload(row):
        return str(row.get(field_schema.name, "")).endswith(str(value))

    return hb.ext.QueryFragment(ctx.backend, "ends_with", field_schema.name, payload)


hb.ext.register_handler(
    "demo",
    hb.ShortText,
    "ends_with",
    "inmem",
    "suffix-index",
    compile_ends_with,
)
Provider-native handler plugins live in heavenbase.handlers.plugins and register through register_handler_plugin. Built-in seeding consumes registries and plugins instead of hard-coded provider lists.

5. Discover Capabilities

Users and extension UIs should discover choices through the public capability index instead of reading registry internals:
import heavenbase as hb

ws = hb.HeavenBase("demo", preset="debug")

hb.capabilities.logical_types()
hb.capabilities.strategies(hb.Vector)
hb.capabilities.backends(hb.Array, hb.SideTable, op="array_contains")
hb.capabilities.ops(hb.ShortText, hb.InlineColumn, backend="sqlite")

# Same methods on ws.capabilities(), filtered to configured backends
ws.capabilities.backends(hb.Vector, hb.VectorIndex, op="near")
Each option exposes to_dict() for building pickers or diagnostics, and workspace construction mirrors common registry metadata into sys-metaschema.

6. Validation Checklist

After adding an extension surface:
  1. Register the component in the correct role package under src/heavenbase/.
  2. Export it from the package __init__.py and top-level heavenbase when it is built in.
  3. Add tests in tests/test_extensions.py, tests/test_backends.py, or a focused test module.
  4. Run rtk bash scripts/test.bash from the HeavenBase repository root.
See demos/developer/03_write_an_extension.py for a minimal custom extension demo.

Further Exploration

Related resources: