Skip to main content

CoreProtocol — The Coordinator

The core is the bus. It connects stacks, plugins, and the model. It has an ID. It routes operations. It manages the composition. The core is not “the entity.” The entity is the composition. But the core is what holds the composition together.
The core_id persists across reconfigurations. Swap the model, change the stacks, add or remove plugins — the core_id stays the same.

CoreProtocol Interface

The default implementation is kernle.entity.Entity.

Properties

PropertyTypeDescription
core_idstrPersistent identifier for this core
modelOptional[ModelProtocol]Currently bound model (None if not configured)
active_stackOptional[StackProtocol]Currently active stack (None if none attached)
stacksdict[str, StackInfo]All attached stacks, keyed by stack_id
pluginsdict[str, PluginInfo]All loaded plugins, keyed by name

Model Management

MethodSignatureDescription
set_model(model: ModelProtocol) -> NoneBind a model. Replaces any previous model. Notifies all stacks via on_model_changed.

Stack Management

MethodSignatureDescription
attach_stack(stack, *, alias=None, set_active=True) -> strAttach a stack. Returns the stack_id. Raises ValueError on duplicate.
detach_stack(stack_id: str) -> NoneDetach a stack. The stack continues to exist independently.
set_active_stack(stack_id: str) -> NoneSwitch which attached stack receives routed operations.

Plugin Management

MethodSignatureDescription
load_plugin(plugin: PluginProtocol) -> NoneActivate a plugin. Creates PluginContext, registers tools.
unload_plugin(name: str) -> NoneDeactivate and remove a plugin.
discover_plugins() -> list[PluginInfo]Scan entry points for available plugins.

Routed Memory Operations

MethodKey Parameters
episode()objective, outcome, *, lessons, tags, source, context
belief()statement, *, type, confidence, foundational, source
value()name, statement, *, priority, type, foundational
goal()title, *, description, goal_type, priority
note()content, *, type, speaker, tags, protect, source
drive()drive_type, *, intensity, focus_areas — upsert by drive_type (v0.13.12)
relationship()other_stack_id, *, trust_level, notes, entity_type — upsert by entity_name (v0.13.12)
raw()blob, *, source

Upsert Semantics (v0.13.12)

drive() and relationship() use get-then-atomic-update-or-create:
  • drive() — Looks up an existing drive by drive_type. If found, updates intensity, focus_areas, and other fields in place. The original id, created_at, and version chain are preserved. Only explicitly provided optional fields are overwritten.
  • relationship() — Looks up an existing relationship by entity_name (other_stack_id). If found, updates fields in place and increments interaction_count. If not found, creates a new relationship.
Both methods use atomic updates with optimistic locking. VersionConflictError propagates if the record was modified between read and write.

Routed Search, Load, and Status

MethodSignatureDescription
search(query, *, limit=10, record_types=None, context=None) -> list[SearchResult]Semantic search on active stack
load(*, token_budget=8000, context=None) -> dict[str, Any]Assemble working memory, then call on_load() on all plugins
status() -> dict[str, Any]Full system status including core, model, stacks, and plugins

Trust Operations

MethodSignature
trust_set(entity, domain, score, *, evidence=None) -> str
trust_get(entity, *, domain=None) -> list[TrustAssessment]
trust_list(*, domain=None, min_score=None) -> list[TrustAssessment]

Checkpoint & Binding

MethodSignatureDescription
checkpoint(message="") -> strSave composition checkpoint, returns checkpoint_id
from_checkpoint(path: Path) -> CoreProtocolRestore from checkpoint file (classmethod)
get_binding() -> BindingSnapshot current composition (used internally by checkpoint)

Stack Management

The core manages multiple stacks with one active at a time. All routed operations go to the active stack.
from kernle.entity import Entity
from kernle.stack import SQLiteStack

# Create the core
entity = Entity(core_id="my-entity")

# Attach stacks (returns stack_id, alias is cosmetic)
entity.attach_stack(SQLiteStack("primary"), alias="primary")
entity.attach_stack(SQLiteStack("work"), alias="work", set_active=False)

# Switch context (by stack_id)
entity.set_active_stack("work")

# Detach when done (by stack_id)
entity.detach_stack("work")
When a stack is attached, the core calls stack.on_attach(core_id, inference_service) so the stack can pass the inference service to its components. When detached, stack.on_detach(core_id) clears inference access.

Plugin Lifecycle

Plugins follow a strict lifecycle managed by the core.

Protocol Version Enforcement

The core checks the plugin’s protocol_version property on load:
# From Entity.load_plugin()
plugin_pv = getattr(plugin, "protocol_version", None)
if plugin_pv is not None and plugin_pv > PROTOCOL_VERSION:
    raise ValueError(
        f"Plugin '{plugin.name}' requires protocol version {plugin_pv}, "
        f"but this core supports version {PROTOCOL_VERSION}."
    )
elif plugin_pv is not None and plugin_pv < PROTOCOL_VERSION:
    logger.warning(
        "Plugin '%s' uses protocol version %d (current: %d).",
        plugin.name, plugin_pv, PROTOCOL_VERSION,
    )
This ensures that a plugin built for a newer protocol cannot silently break on an older core. Older plugins are loaded with a warning since the core is expected to maintain backward compatibility.

Operation Routing

When you call entity.episode(...), the core does not just forward to the stack. It enforces provenance first. The provenance step populates:
  • id: New UUID
  • stack_id: From the active stack
  • source_entity: "core:{core_id}" or a custom source (plugins use "plugin:{name}")
  • source_type: Explicit on all write paths. Accepts a string or SourceType enum value. Defaults to "direct_experience" when not specified. Invalid strings raise ValueError. Internal subsystems pass explicit values: checkpoint uses "observation", sync uses "external" with source_entity="kernle:sync", doctor uses "observation" with source_entity="kernle:doctor".
  • created_at: Current UTC timestamp
  • derived_from: Optional lineage chain
  • context / context_tags: Optional grouping
This is why writing through the core matters. Direct stack.save_*() calls skip provenance, producing memories with incomplete attribution.
# The proper way -- full provenance
entity.episode(
    "Deploy to production",
    "Successful with zero downtime",
    lessons=["Blue-green deploys work well"],
    tags=["deployment"],
    context="work",
)

# Bypass -- incomplete provenance (for migration/repair only)
stack.save_episode(Episode(
    id="...",
    stack_id=stack.stack_id,
    objective="Deploy to production",
    outcome="Successful",
    # No source_entity, no context, no source_type
))

Checkpoint Save and Restore

The checkpoint() method saves the current composition to a timestamped JSON file. from_checkpoint() restores an Entity from a saved checkpoint.
# Save current composition
cp_id = entity.checkpoint("before refactor")
# Saves to ~/.kernle/checkpoints/{core_id}_{timestamp}_{uuid}.json

# Restore later
from pathlib import Path
restored = Entity.from_checkpoint(Path.home() / ".kernle/checkpoints/my-entity_20260216_...json")
Checkpoint files include:
  • schema_version — format version (currently 1) for forward compatibility
  • checkpoint_id — unique identifier for this checkpoint
  • message — human-readable description
  • binding — the composition snapshot (core_id, model_config, stacks, plugins)
  • created_at — UTC timestamp
Schema versioning policy:
  • Missing schema_version is treated as version 1 with a warning (backward compat for checkpoints created before this change)
  • schema_version > 1 raises ValueError (forward compat — prevents loading newer formats with older code)
Restore semantics: Restore is best-effort. Individual stack, plugin, or model failures during rehydration are logged at WARNING level but do not abort the restore. A partially restored entity is better than no entity in alpha. Retention: Each checkpoint() call retains the 10 most recent checkpoints per core_id, removing older ones automatically.