Skip to content

Kernel & Runtime

The Kernel is the mediator that connects the five ports and the hook registry. Runtime is a thin convenience wrapper that owns a kernel plus an event loop for host code that wants a batteries-included entry point.

Kernel

dna.kernel.Kernel

Kernel(*, tenant: str | None = None)

Mediator that orchestrates 5 ports + hooks to produce ManifestInstance.

Create a Kernel.

tenant binds this kernel to a tenant — all subsequent write/read ops route to that tenant's storage. Use with_tenant(other) for per-call cross-tenant operations (Stripe Connect pattern). Pass None (default) for the unbound kernel — only GLOBAL kinds may be written; TENANTED kinds raise TenantRequired.

active_event_bus property

active_event_bus

The KernelEventBus registered via event_bus(), or None.

active_source property

active_source: 'SourcePort | None'

The SourcePort registered via source() / storage(), or None.

Read-only accessor. The setter method is source(src). Named active_source to avoid collision between a method and a property of the same name.

source_type property

source_type: str

Source adapter class name (empty string when no source wired) — safe for capability checks. Thin facade over the SourceFacade collaborator (s-kernel-decomp-f5-satellites).

active_writers property

active_writers: tuple['WriterPort', ...]

WriterPorts registered via writer(w). Read-only view — mutating the returned tuple has no effect on the Kernel's internal list.

active_readers property

active_readers: tuple['ReaderPort', ...]

ReaderPorts registered via reader(r). Read-only view — mirror of active_writers (s-dna-rw-roundtrip-suite: the round-trip conformance kit enumerates registered pairs through this surface).

embedding_dims property

embedding_dims: int

Output dimensionality of the active embedding provider.

embedding_model_id property

embedding_model_id: str

Identity of the active embedding space (vectors from different model_ids are NOT comparable).

register_main_loop

register_main_loop(loop: AbstractEventLoop) -> None

Register the long-lived event loop that owns this kernel's source pool. After registration, sync API calls from worker threads dispatch back to this loop via run_coroutine_threadsafe instead of spawning a new loop. Idempotent.

with_tenant

with_tenant(tenant: str | None) -> 'Kernel'

Return a shallow-copy Kernel bound to tenant.

Original Kernel is unchanged — call sites can hand off the copy to per-request handlers without mutating shared state (Sanity client.withConfig({dataset: X}) pattern).

Pass tenant=None to obtain an unbound kernel (writes only allowed for GLOBAL kinds).

kind_port_for

kind_port_for(
    kind: str, *, api_version: str | None = None
) -> KindPort | None

Public lookup for a registered KindPort by kind name.

Use from tooling that needs to consult Kind metadata (is_runtime_artifact, scope, storage, ...) without reaching into Kernel internals. Returns None if the kind isn't registered. Pass api_version= for exact resolution when the kind name is ambiguous (i-195 — e.g. the Reference pair).

kind_plane

kind_plane(
    kind: str, *, api_version: str | None = None
) -> str

Two-planes (spec 2026-06-09): the declared plane of a Kind by name — 'record' or 'composition'. Unknown Kinds default to 'composition' (fail-safe: behaves exactly as today). Pass api_version= for exact resolution on ambiguous names (i-195).

kind_ports

kind_ports() -> list[KindPort]

All registered KindPorts. Order matches registration.

embeddable_kinds

embeddable_kinds() -> frozenset[str]

F3 D4 (spec 2026-06-10-kinds-descriptor-f3): kind names whose port declares embed_fields — via descriptor embed: or a class-level embed_fields (the KindBase parity hook for not-yet-migrated classes). The embeddings sidecar derives its eligible set from this instead of a hardcoded frozenset (dna_shared.embeddings.embeddable_kinds unions it with the shrinking legacy fallback).

use

use(hook: 'HookName', fn: Any) -> None
use(hook: str, fn: Any) -> None
use(hook: str, fn: Any) -> None

Register middleware on a hook point (e.g., 'pre_build_prompt').

on

on(hook: 'HookName', fn: Any) -> None
on(hook: str, fn: Any) -> None
on(hook: str, fn: Any) -> None

Register event subscriber (e.g., 'post_save').

on_veto

on_veto(
    hook: "HookName",
    fn: Any,
    *,
    priority: int = ...,
    key: str | None = ...
) -> None
on_veto(
    hook: str,
    fn: Any,
    *,
    priority: int = ...,
    key: str | None = ...
) -> None
on_veto(
    hook: str,
    fn: Any,
    *,
    priority: int = 0,
    key: str | None = None
) -> None

Register a veto listener (e.g., 'pre_save') — raising vetoes the operation. See HookRegistry.on_veto for priority/key semantics.

source

source(source: SourcePort) -> None

Register THE SourcePort — where manifest documents live (filesystem, SQLite, Postgres...). One per kernel; registering again replaces it.

This is a boot gate, not a plain setter: the port is structurally validated here (validate_source_port) so a malformed source fails loudly at registration with the missing members named, instead of deep inside the first load_all. Sources that are KernelAttachable are auto-attached (fail-soft) so direct kernel.source(s) callers get the same reader wiring as Kernel.auto(source=s).

on_write

on_write(callback) -> None

Register a callback invoked after each successful write_document or delete_document. Callback signature: callback(scope: str, kind: str, name: str, op: Literal["write", "delete"]) -> None

Used by long-lived holders (e.g., MIHolder) to invalidate their own caches when Temporal workers or other in-process writers mutate docs.

Observers must not raise — exceptions are swallowed to avoid breaking the write path.

DEPRECATED in Phase 15.1 PR4 (planned): superseded by register_holder() + cross-process KernelEventBus. Remains available through PR3 for back-compat with existing wiring.

register_holder

register_holder(holder) -> None

Register a long-lived MIHolder so cross-process invalidation events from KernelEventBus can reach it.

On kernel.invalidate(scope=..., ...), every registered holder whose holder.scope matches gets holder.reload() called.

Idempotent: re-registering the same holder is a no-op.

unregister_holder

unregister_holder(holder) -> None

Unregister a holder. Idempotent — unregistering an unknown holder is silently ignored.

event_bus

event_bus(bus) -> None

Register a KernelEventBus implementation. The harness/worker bootstrap calls await bus.start(kernel) after registration.

Registering a bus is a declarative step; it does NOT auto-start the bus (start needs an active event loop). Callers control lifecycle. Replacing a previously registered bus replaces the reference but does NOT stop the previous bus — callers manage teardown explicitly.

batch_writes

batch_writes()

Suppress per-write reload + observer fan-out for the duration of the block. On exit, fires ONE consolidated invalidation per touched scope.

Solves the architectural problem where N sequential writes each triggered a full scope reload (load_all of hundreds of docs + bundle entries). Pre-2026-05-11 a 22-Story backfill cost 22 reloads × ~10s each = ~4min. With this context manager, same backfill = 1 reload at exit = ~10s.

Usage: with kernel.batch_writes(): kernel.write_document(scope, k, n, raw1) kernel.write_document(scope, k, n, raw2) ... # → exit fires one invalidate per touched scope

Reentrant: nested batch_writes() blocks coalesce — only the outermost block's exit fires the consolidated invalidation.

Caveats: - Reads INSIDE the block see stale MI (no reload between writes). For backfill loops that don't re-read what they just wrote, this is fine. For workflows that read-modify-write, structure each pair OUTSIDE the block (or accept the staleness). - Cross-process invalidation (EventBus → other harness instance) still fires per-write through the outbox; only the LOCAL holder reload + observer fan-out is suppressed.

Delegates to self._invctl (s-kernel-decompose-god-object). Returns the controller's context manager (state stays on the kernel).

invalidate

invalidate(
    *,
    scope: str,
    tenant: str = "",
    kind: str,
    name: str,
    op: str
) -> None

Invalidate all in-process caches for a (scope, tenant) tuple in response to a write/delete event from the EventBus.

Idempotent — calling for the same event multiple times is safe; the EventBus replay logic relies on this.

Phase 15.1 contract: 1. Drop _base_instance_cache[scope] if present. 2. For each registered holder with matching scope, call holder.reload(). 3. Skip if kind == "Evidence" (preserves the audit-churn- avoidance rule from the legacy _reload_on_write; centralized here so future authors don't need to remember it on each subscription site).

Note: tenant is currently informational. Phase 15.2 (MIHolder immutability + tenant-resolved view caching) consumes this so a tenant-specific overlay invalidation can drop only the matching resolved view instead of the whole holder.

batch_writes(): when called from inside a batch_writes() block (depth > 0), this records the event but skips the expensive reload + observer fan-out. The outermost block's exit drains the pending list with one consolidated invalidate per scope.

Delegates to self._invctl (s-kernel-decompose-god-object).

list_scopes_async async

list_scopes_async() -> list[str]

Proxy to source.list_scopes() — normalises sync (FS) vs async (SQLite/Postgres) adapters. Thin facade over the SourceFacade collaborator (s-kernel-decomp-f5-satellites).

source_metadata

source_metadata() -> dict

Read-only whitelisted snapshot of source adapter metadata (type / dsn / schema / base_dir; private state stays in the adapter). Thin facade over the SourceFacade collaborator (s-kernel-decomp-f5-satellites).

preview_document async

preview_document(
    scope: str, kind: str, name: str, raw: dict
) -> PreviewResult

Pure preview — returns target, serialized files, exists_already.

Does NOT touch disk. exists_already is a UI hint so callers can render "create" vs "overwrite" affordances.

write_document async

write_document(
    scope: str,
    kind: str,
    name: str,
    raw: dict,
    author: str | None = None,
    skip_hooks: bool = False,
    *,
    tenant: str | None = None,
    layer: tuple[str, str] | None = None,
    invalidate_mode: str = "scope",
    write_class: str = "substantive"
) -> str | None

Persist a document through the registered WritableSourcePort.

Public facade (Fase 2, s-kernel-decomp-f2-writepipeline): this method owns the guardrails — invalidate_mode validation, the _REMOVED_KINDS block, the record-plane scope→doc demotion (two-planes F1, resolved from the doc's own apiVersion — i-195), and the OTel kernel.write_document span. The FAT execution (tenant resolve, capability-gated adapter kwargs, layer-policy check, the pre_save veto gate, persist, and the ordered invalidation fan-out) lives in WritePipeline.write — see its docstring for the full write contract (tenant/layer semantics, the three invalidate tiers, the always-on observer fan-out).

Returns the adapter version id (or None if the adapter is version-less). Emits post_save on success unless skip_hooks is True.

Raises: NotWritableError — no / read-only source. TenantRequired — TENANTED kind without a tenant. TenantNotAllowed — GLOBAL kind with a tenant. InvalidTenantSlug — tenant has invalid characters or is reserved. LayerPolicyViolationError — declared policy forbids the write. ValueError — invalidate_mode not in {scope, doc, none}. KindRetiredError — Kind is in _REMOVED_KINDS (writes blocked).

delete_document async

delete_document(
    scope: str,
    kind: str,
    name: str,
    author: str | None = None,
    skip_hooks: bool = False,
    *,
    tenant: str | None = None,
    layer: tuple[str, str] | None = None,
    invalidate_mode: str = "scope",
    api_version: str | None = None
) -> None

Delete a document through the registered WritableSourcePort.

Tenant resolution mirrors write_document. See its docstring for the full contract — invalidate_mode also follows the same semantics (scope | doc | none, default scope).

cache

cache(cache: CachePort) -> None

Register THE CachePort — where resolved external dependencies are installed (e.g. FilesystemCache under .dna/cache/). One per kernel; dependency resolution writes through it and instance loading reads installed items back.

resolver

resolver(scheme: str, resolver: ResolverPort) -> None

Register a ResolverPort for a URI scheme ("local", "github", "http"...) — how external dependencies declared in a Genome are fetched. One resolver per scheme; registering the same scheme again replaces the previous resolver.

reader

reader(r: ReaderPort) -> None

Register a ReaderPort — a bundle-format detector/parser (SKILL.md, SOUL.md, AGENTS.md...). Readers are tried in registration order during scans.

H1 conformance gate: the object must satisfy the runtime-checkable ReaderPort Protocol, so a typo'd detect/read fails here (ReaderRegistrationError) instead of in production scans. Re-registering the same class is an idempotent no-op.

writer

writer(w: WriterPort) -> None

Register a WriterPort — the serialize/write half of a bundle format, mirror of reader().

Same H1 conformance gate: raises WriterRegistrationError unless the object satisfies the WriterPort Protocol (can_write/write/serialize — serialize is part of the contract since s-dna-rw-roundtrip-suite). Re-registering the same class is an idempotent no-op.

kind

kind(k: KindPort) -> None

Register a KindPort. Thin facade over the H1 validation funnel (Protocol / dup-key / dup-alias / BUNDLE-marker / plane-lint / i-195 name-collision + alias generation), which lives in the KindRegistry collaborator (s-kernel-decomp-f3-kindregistry).

kind_from_descriptor

kind_from_descriptor(raw: dict[str, Any]) -> KindPort

Register a BUILTIN Kind from a kinds/*.kind.yaml descriptor (KindDefinition package data). Thin facade over the KindRegistry funnel (s-kernel-decomp-f3-kindregistry); returns the registered port.

tool

tool(td: ToolDefinition) -> None

Register a tool definition (delegates to self._toolreg).

get_tool

get_tool(name: str) -> 'ToolDefinition | None'

Return a tool definition by name, or None if unknown.

get_tools

get_tools(
    *,
    group: str | None = None,
    groups: list[str] | set[str] | None = None
) -> list[ToolDefinition]

Return registered tool definitions, optionally filtered by group(s). Delegates to self._toolreg (s-kernel-decompose-god-object).

list_tool_groups

list_tool_groups() -> dict[str, list[str]]

Reverse-build {group: [tool_names...]} from the registry.

describe_kind

describe_kind(kind_name: str) -> dict[str, Any] | None

Return a summary dict for a registered kind, including resolved docs. Delegates to self._kindreg.

composition_profile

composition_profile(profile) -> None

Register a composition profile that declares how an orchestrator kind connects to other kinds.

resolve_dep_filter_target

resolve_dep_filter_target(value: str)

Canonical dep_filter target resolution — alias contract + deprecated legacy kind= shim. Delegates to self._kindreg (s-unify-composition-subsystems; TS twin: Kernel.resolveDepFilterTarget).

validate_dep_filters

validate_dep_filters() -> None

s-alias-generated-not-typed — every dep_filter target of an EXTENSION-registered Kind must resolve to a registered alias. Thin facade over the KindRegistry funnel (s-kernel-decomp-f3-kindregistry); raises KindRegistrationError on an unknown/legacy extension target, warns for per-scope declarative ports. Called at the end of Kernel.auto(); harness boots hit it too.

load

load(ext: Extension) -> None

Load an Extension — the way Kinds, readers, writers and tools enter the kernel (Kernel.auto() calls this for every entry-point-discovered extension).

Validates the WHOLE Extension contract fail-loud before calling ext.register(self) (s-dna-extension-host-contract): a callable register() plus non-empty name and version — a blank name used to be accepted silently and resurface later as None alias owners. During register() the extension's alias_owner/name is the owner context for generated Kind aliases (s-alias-generated-not-typed). Registration-validation errors (duplicate Kind, malformed reader/writer) always propagate — they are configuration problems the operator must fix; other runtime errors route to the extension_error hook when one is subscribed, and propagate otherwise.

list_templates

list_templates() -> list[Template]

Aggregate templates() from every loaded extension.

The templates() method is feature-tested via hasattr so extensions that predate Phase 0 (and don't declare the method) still work. A misbehaving extension that raises inside its templates() is logged as a warning but never breaks discovery for the other extensions.

scaffold

scaffold(
    template_id: str,
    target_root: Path,
    on_conflict: OnConflict = "error",
) -> list[Path]

Materialize a template by id into target_root.

Raises KeyError if no loaded extension advertises a template with the given id. on_conflict is passed through to :func:dna.kernel.templates.materialize.

container_for_kind

container_for_kind(kind_name: str) -> 'str | None'

Return the storage container directory for a kind, or None. Delegates to self._kindreg.

storage_for_kind

storage_for_kind(
    kind_name: str,
) -> "StorageDescriptor | None"

Return the StorageDescriptor for a kind, or None. Delegates to self._kindreg.

fetch_bundle_entry

fetch_bundle_entry(
    scope: str,
    kind: str,
    name: str,
    entry: str,
    *,
    tenant: str | None = None
) -> bytes

Phase 14w — fetch a binary entry from a bundle through the source adapter (port-respecting; works on filesystem today, SQLite/Postgres when those adapters land their impls).

Use case: tools (and the harness REST surface) reading large artifacts that the kernel did NOT inline into doc.spec — most notably the graph.json payload of a GraphifyArtifact bundle (Phase 14w). Resolves kindcontainer via the registered KindPort's StorageDescriptor and delegates the read to source.fetch_bundle_entry(...).

Honors tenant overlay routing: when tenant is passed and the adapter supports it, the tenant copy is preferred over the base layer.

Raises: - ValueError if the kind is not registered. - NotImplementedError if the source adapter doesn't implement bundle entry fetch (acceptable until SQL adapters ship the method). - FileNotFoundError if the bundle or entry is absent.

Delegates to self._bundleio (s-kernel-decompose-god-object).

fetch_bundle_entry_async async

fetch_bundle_entry_async(
    scope: str,
    kind: str,
    name: str,
    entry: str,
    *,
    tenant: str | None = None
) -> bytes

Async variant of fetch_bundle_entry. Delegates to self._bundleio (s-kernel-decompose-god-object).

write_bundle_entry_async async

write_bundle_entry_async(
    scope: str,
    kind: str,
    name: str,
    entry: str,
    content: bytes | str,
    *,
    tenant: str | None = None
) -> None

Persist a single bundle entry payload via the active source.

Use this instead of touching kernel._source + _pool directly. Source-agnostic: dispatches to whichever adapter is active (FS / SQLite / Postgres), so the caller doesn't need to know the backing store.

The bundle entry write happens AFTER the parent doc exists (caller must have done write_document first). The doc owns the tenant identity — passing the same tenant here keeps the bundle row's tenant column aligned with the dna_documents row, so subsequent delete_document sees and cleans both atomically.

Bug-of-record: 2026-05-21 — multiple tools were doing INSERT INTO dna_bundle_entries with hardcoded tenant='' while the doc index had the real tenant. The delete couldn't find the bundle row (tenant mismatch) and bytes leaked. This API closes that gap.

Raises: - ValueError if the kind is unknown or has no bundle container. - NotImplementedError if the source adapter doesn't declare BundleEntryWritable.

Delegates to self._bundleio (s-kernel-decompose-god-object).

digest_manifest async

digest_manifest(
    scope: str,
    *,
    tenant: str | None = None,
    include: "Callable[[dict], bool] | None" = None,
    source: "Any | None" = None
) -> "dict[tuple[str, str], str]"

s-sync-s2 — content map of a scope: {(kind, name): digest}.

Each digest is the Kind-aware canonical_digest (s-sync-s1) of the doc's authored identity, combined with a Merkle hash of its non-marker bundle entries (so binary assets — fonts, images — are covered too). Source-independent by construction: the SAME scope in two sources (FS git ↔ Postgres runtime) yields IDENTICAL manifests when in sync, so a diff is a set-diff of two manifests (no content transfer).

include(raw) -> bool optionally filters docs (s-sync-s4 passes an authored-vs-generated predicate). Default: every local doc of the scope. source overrides the source read from (default: the registered one) — the kernel's Kinds drive the digest, so a manifest can be built for ANY source with the current Kind set (s-sync-s4 diffs two sources).

s-kernel-decompose-god-object: delegates to self._sync (SourceSync).

diff_manifests staticmethod

diff_manifests(
    a: "dict[tuple[str, str], str]",
    b: "dict[tuple[str, str], str]",
) -> "dict[str, list[tuple[str, str]]]"

s-sync-s4 — set-diff two digest manifests. Pure + O(n). Delegates to SourceSync.diff_manifests (s-kernel-decompose-god-object).

push_scope async

push_scope(
    scope: str,
    to_source: "Any",
    *,
    tenant: str | None = None,
    include: "Callable[[dict], bool] | None" = None,
    dry_run: bool = False,
    prune: bool = False
) -> "dict[str, list]"

s-sync-s5 — reconcile to_source to match THIS kernel's source (the source-of-truth, e.g. FS git) for scope.

Computes the minimal diff (s-sync-s4) and applies it: each added/changed doc is read from the current source (resolved doc + its bundle entries) and written to to_source via save_document — so the s-sync-s3 atomic net persists doc + bundle entries together. prune deletes docs that exist only in to_source. include (e.g. authored-only) narrows the set. dry_run returns the diff without writing.

Returns {added, changed, removed, applied} where applied lists ("write"|"delete", kind, name). Idempotent: a second push finds an empty diff and applies nothing.

s-kernel-decompose-god-object: delegates to self._sync (SourceSync).

kind_by_container

kind_by_container(container: str) -> 'str | None'

Return the kind name whose StorageDescriptor.container matches. Delegates to self._kindreg (None for empty/unregistered).

serialize_document

serialize_document(
    scope: str, kind: str, name: str, raw: dict
) -> dict

Serialize a document to files without writing. Delegates to self._bundleio (s-kernel-decompose-god-object).

build

build(
    raw_docs: list[dict],
    scope: str,
    layers: dict[str, str] | None = None,
    layer_docs: list[dict] | None = None,
    dep_docs: list[dict] | None = None,
    resolve_errors: list[str] | None = None,
    *,
    skip_async_rescan: bool = False
) -> "ManifestInstance"

Build ManifestInstance from pre-loaded data. Pure computation, no I/O.

For async contexts (server), the caller loads docs via await and passes them here. For sync contexts (CLI), use instance() which handles the async bridging.

skip_async_rescan (set by instance_async when it will run the rescan post-build): suppress the sync rescan path. Delegates to self._builder (s-kernel-decompose-god-object).

list_documents async

list_documents(
    scope: str,
    *,
    kind: str | None = None,
    tenant: str | None = None
) -> list[tuple[str, str]]

Lista (kind, name) de docs no scope. Filtrável por kind.

Não constrói ManifestInstance. Custo ~10ms PG / ~5ms SQLite / ~30ms FS (este último cai no fallback load_all).

Cache: per (scope, kind, tenant) com TTL 30s. Invalidado pelo kernel.write_document via _invalidate_granular_cache. Single- flight via lock — N requests concorrentes na mesma key compartilham a fetch.

get_document async

get_document(
    scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None
) -> dict[str, Any] | None

Carrega UM doc por (scope, kind, name). Retorna raw dict ou None. Delegado ao self._query (s-kernel-decompose-god-object). Cache bounded 2000/TTL 60s + V1 _INHERITABLE_KINDS parent fallback vivem lá; a API pública (Studio reads, agent routes, deps) é intacta.

resolve_document async

resolve_document(
    scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None
)

Resolve a doc through the composition chain — Phase 17 primitive.

Returns ResolvedDocument with merged doc + full provenance.

Bootstrap Kinds (Genome, LayerPolicy, KindDefinition) bypass inheritance entirely — read local-only, single-layer provenance.

For all other Kinds: 1. Look up composition rule for kind in scope's LayerPolicy. 2. Determine resolution chain (walking parent_scope when scope_inheritance=enabled; just [(scope, tenant), (scope, None)] otherwise). 3. For each chain layer, query source via cache. 4. Apply merge strategy (override_full or field_level). 5. Build ResolvedDocument with provenance + is_inherited.

Cache: layer-level via _granular_doc_cached (same TTL/bound as get_document). Resolution chain itself is recomputed each call (cheap — Genome walk).

composition_summary async

composition_summary(
    scope: str, *, tenant: str | None = None
) -> dict

Phase 17 (s-comp-f7-composition-summary, 2026-05-28) — cheap aggregate of the scope's parent chain + per-Kind counts.

Single endpoint replaces N list calls from the Sidebar. Returns:

{
  "scope": "innovec-prod",
  "parent_chain": ["innovec-base", "_lib"],
  "resources": {
    "Agent":  {"local": 1, "inherited": 11, "total": 12},
    "LottieAsset":   {"local": 0, "inherited": 6,  "total": 6},
    ...
  },
}

Performance: each Kind takes 1 source.query (local-only push-down) + 1 source.query (parent push-down dedup) ≈ 10ms ¢. Cached server-side 60s via outer HTTP cache (see API route).

NB: kept inline (NOT extracted to the CompositionResolver collaborator in Fase 5) because it needs the query push-down, and widening CompositionResolverHost with RecordQuery would break the frozen F1 FakeKernelSlice guard (whose composition fake exposes no query). It is a thin aggregation over the query facade + _compute_resolution_chain.

personalize_document async

personalize_document(
    target_scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None,
    overwrite: bool = False
)

Phase 17 (s-comp-f6-personalize-primitive, 2026-05-28) — clone an inherited doc into target_scope as a local override.

Resolves the doc via composition chain; if the effective layer is THIS scope (not inherited), raises ValueError. Otherwise clones spec + bundle entries to target_scope atomically.

Args: target_scope: where the override should land. kind, name: doc identity. tenant: writes go to base layer (tenant=None) by default; pass a tenant slug for tenant-overlay personalization. overwrite: if False (default) raises when target_scope already has a local doc with this name.

Returns: ResolvedDocument of the freshly written local copy.

Raises: ValueError: doc isn't inherited / target already exists (without overwrite=True).

query async

query(
    scope: str,
    kind: str,
    *,
    filter: dict | None = None,
    projection: list[str] | None = None,
    limit: int | None = None,
    offset: int | None = None,
    order_by: list[str] | None = None,
    tenant: str | None = None,
    origin: str = "all",
    scopes: list[str] | None = None
)

Marco A kernel-level query — push-down delegado ao source.

Delegado ao self._query (s-kernel-decompose-god-object). Tenant auto-stamp (kwarg > Kernel.tenant > None), origin filter (local/inherited/all), a chain de scope-inheritance e o cross-scope scopes= (F2.4 — queries locais por scope, concat sem dedup; scopes ganha do scope posicional) vivem lá. Mantido como async generator aqui pra preservar a assinatura exata (callers fazem async for + inspect.isasyncgenfunction). API intacta.

count async

count(
    scope: str,
    kind: str,
    *,
    filter: dict | None = None,
    group_by: str | None = None,
    tenant: str | None = None,
    scopes: list[str] | None = None
) -> dict

F2 D2 — aggregation count público ao lado de query.

Push-down ao source (PG: SELECT count(*) … GROUP BY nativo; FS/SQLite: protocol-default). Retorna CountResult: {"total": int, "groups": [{"key", "count"}] | None} (groups por count DESC, key ASC None-last).

SEM origin de propósito — records são por-scope; herança não se aplica a count (spec D5). Cross-scope via scopes= (soma totals + merge de groups por key; ganha do scope posicional).

Example (Studio velocity): res = await kernel.count( "dna-development", "Story", group_by="spec.status", ) # {"total": 950, "groups": [{"key": "done", "count": 700}, …]}

record_search_provider

record_search_provider(provider) -> None

Register the semantic-search provider (two-planes F2). One per kernel; later registration replaces (boot-time wiring) and resets the failure-warning damper (new provider → fresh episode).

embedding_provider

embedding_provider(provider) -> None

Register the embedding provider (rec-embedding-port). One per kernel; later registration replaces (boot-time wiring). Sibling to record_search_provider — a real provider (ONNX all-MiniLM-L6-v2) registers itself at app boot; without one, embed uses the deterministic FakeEmbeddingProvider floor.

embed async

embed(texts: list[str]) -> list[list[float]]

Embed texts into dense vectors (rec-embedding-port). Uses the registered EmbeddingPort when present, else the deterministic zero-dep fake floor. Returns one dims-length vector per input, in order; empty input → empty list. Read the space via kernel.embedding_dims / kernel.embedding_model_id.

search async

search(
    scope: str,
    query_text: str,
    *,
    kind: str | None = None,
    k: int = 10,
    tenant: str | None = None
) -> dict[str, Any]

Public record search (F2 D2). Provider registered → semantic (pgvector/RRF, degraded=False); no provider OR provider error → lexical token-match fallback (degraded=True). Thin facade over the SearchEngine collaborator (s-kernel-decomp-f5-satellites).

query_list_sync

query_list_sync(
    scope: str,
    kind: str,
    *,
    filter: dict | None = None,
    tenant: str | None = None
) -> list[Document]

Sync wrapper around query returning parsed Document objects (drop-in for mi.all(kind)). Delegado ao self._query (s-kernel-decompose-god-object); _run_sync_helper + main-loop binding vivem lá. API intacta (CLI, workers, tool executors).

get_document_sync

get_document_sync(
    scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None
) -> Document | None

Sync wrapper around get_document returning a parsed Document (drop-in for mi.one(kind, name)). Delegado ao self._query (s-kernel-decompose-god-object). API intacta.

instance

instance(
    scope: str, layers: dict[str, str] | None = None
) -> "ManifestInstance"

Sync wrapper around instance_async. Use this from sync contexts (CLI, tests). From inside an event loop, prefer await kernel.instance_async(scope, layers) directly to avoid the asyncio.run-in-thread fallback that orphans pool-based adapters. Delegates to self._builder (s-kernel-decompose-god-object).

instance_async async

instance_async(
    scope: str,
    layers: dict[str, str] | None = None,
    *,
    lazy: bool | None = None
) -> "ManifestInstance"

Async-native version of instance. Use directly from async contexts (FastAPI lifespan, Temporal activities) to keep the source pool tied to the caller's event loop.

Phase 9: tenant binding auto-promotes into layers so load_bootstrap_docs + load_layer pick up the right tenant overlay.

Story s-miholder-transient (2026-05-14): lazy kwarg lets callers explicitly opt into lazy MI construction (bootstrap docs only; mi.all/one delegate to kernel.query). Default None honors DNA_LAZY_MI env var. True/False override.

resolve_layers

resolve_layers(
    mi: "ManifestInstance", layers: dict[str, str]
) -> "ManifestInstance"

Resolve layers on an existing MI (sync wrapper). Delegates to self._builder (s-kernel-decompose-god-object).

resolve_layers_async async

resolve_layers_async(
    mi: "ManifestInstance", layers: dict[str, str]
) -> "ManifestInstance"

Async-native layer resolver — MI.resolve_async() delegates here, then through self._builder (s-kernel-decompose-god-object).

model_profile async

model_profile(model_id_or_alias: str) -> dict | None

Resolve a ModelProfile from the _lib registry by model_id, then by aliases[]. Returns the RAW DICT row (callers read profile["spec"][...]) or None. _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator (s-kernel-decomp-f5-satellites).

voice_policy async

voice_policy(name: str = 'default') -> dict | None

Resolve a VoicePolicy from the _lib registry by metadata name (falls back to the first policy). Returns the RAW DICT row or None. _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator (s-kernel-decomp-f5-satellites).

embedding_profile async

embedding_profile(name: str = 'default') -> dict | None

Resolve the embedding profile from the _lib CognitivePolicy by name. Returns a RAW-DICT-shaped row whose spec is the doc's embedding section, or None. _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator (s-kernel-decomp-f5-satellites).

quick classmethod

quick(
    scope: str, base_dir: str = ".dna"
) -> "ManifestInstance"

Quick-start: a filesystem Kernel with every discoverable extension loaded, returning the ManifestInstance for scope. Thin facade over build_quick_manifest (kernel decomposition, Fase 4 — s-kernel-decomp-f4-bootstrap). cls is threaded through so a subclass (e.g. Runtime) is what gets built.

auto classmethod

auto(source=None) -> 'Kernel'

Create a Kernel with all discoverable extensions loaded.

Thin facade over build_auto_kernel (kernel decomposition, Fase 4 — s-kernel-decomp-f4-bootstrap): entry-point discovery + H8 deterministic topo-sort boot ordering + source/cache/resolver wiring + validate_dep_filters gate. cls is threaded through so a subclass (e.g. Runtime) is what gets built. See the collaborator module for the full recipe + rationale.

Runtime

dna.kernel.runtime.Runtime

Runtime(*, tenant: str | None = None)

Bases: Kernel

Runtime that integrates any agent configuration standard.

storage

storage(s: SourcePort) -> None

Register a storage backend. Alias for source().

manifest

manifest(scope: str, layers: dict[str, str] | None = None)

Load a manifest for a scope. Alias for instance().

manifest_async async

manifest_async(
    scope: str, layers: dict[str, str] | None = None
)

Async variant of manifest(). Use from inside an event loop to avoid the asyncio.run-in-thread fallback that orphans pool- based source adapters (asyncpg).