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,
    allow_personal: bool = False
)

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.

allow_personal authorizes binding a reserved personal:<oid> partition (ADR-personal-memory) — it is False for every ordinary caller (the personal: scheme is rejected as user input); only the personal-memory write path sets it, and it travels with this kernel so the write pipeline's slug validation permits the personal partition.

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).

search_provider property

search_provider

The registered RecordSearchProvider, or None.

The READ half of record_search_provider, and it exists for exactly one reason: search()'s envelope carries a single degraded flag, and two very different situations set it — this deployment has no semantic plane at all and the semantic plane is registered and just failed. A caller that must tell those apart (a door reporting WHAT it lost, s-porta-de-busca) otherwise had to reach for _search_provider through the underscore, which is how a private became a de-facto public in three places already.

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, *, allow_personal: bool = False
) -> "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).

allow_personal authorizes binding a reserved personal:<oid> partition (ADR-personal-memory) — the personal-memory write path is the sole caller that sets it. The flag travels with the returned kernel so the downstream write pipeline permits the personal slug (every other binding leaves it False, and the personal: scheme stays rejected).

kinds_for_scope

kinds_for_scope(
    scope: str | None,
) -> dict[tuple[str, str], KindPort]

The (api_version, kind) → port map as scope sees it: the GLOBAL Kinds (extensions + builtin descriptors) plus the ones this scope's own store declared.

The scoped counterpart of the _kinds property. A store-loaded Kind governs one scope (i-081), so anything that decides behaviour FOR a scope — the kinds map a ManifestInstance is built with, the digest of a scope's instances — reads this instead of _kinds.

kind_port_for

kind_port_for(
    kind: str,
    *,
    api_version: str | None = None,
    scope: 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).

Pass scope= whenever the answer will DECIDE something for that scope — a store-loaded Kind governs only the scope whose store declared it (i-081), and an unscoped lookup sees every scope's Kinds.

validate_instance

validate_instance(
    scope: str,
    kind: str,
    name: str,
    raw: dict,
    *,
    api_version: str | None = None
) -> None

Validate raw['spec'] against the Kind's declared JSON Schema — the SAME check write_instance enforces on the write path, exposed as a public pre-flight so read-only callers (dna instance apply --dry-run) catch a schema-violating doc BEFORE the write (i-validation-shallow).

Raises SpecValidationError on violation, honouring DNA_WRITE_VALIDATION (enforce vetoes, warn logs, off skips). Kinds without a schema stay permissive. Thin delegator to WritePipeline._validate_spec_schema so dry-run and apply can never drift.

kind_plane

kind_plane(
    kind: str,
    *,
    api_version: str | None = None,
    scope: 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(*, scope: str | None = None) -> list[KindPort]

All registered KindPorts. Order matches registration. scope narrows to the Kinds that govern that scope (i-081).

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).

kinds_with_trait

kinds_with_trait(trait: str) -> frozenset[str]

Kind NAMES whose port declares trait — the lookup that replaces a literal Kind-name list.

The third instance of a mechanism the SDK already had twice — :meth:_classify_kinds (derived, but only for booleans the kernel knows by name) and register_schema_fragment (open + namespaced, but only for schema) — generalised: an OPEN vocabulary resolved by lookup. A consumer asks "which Kinds are work items?" and the Kinds answer, so adding one to a family is a declaration on that Kind instead of an edit in every module that has an opinion about it.

Returns names, not ports, because that is what the callers it replaces used (kind in _DIGEST_KINDS). Use :meth:kind_ports_with_trait when you need the port itself.

kind_ports_with_trait

kind_ports_with_trait(trait: str) -> list[KindPort]

The registered ports declaring trait, in registration order.

traits_of

traits_of(
    kind: str, *, api_version: str | None = None
) -> frozenset[str]

Every trait declared by kindfrozenset() when the name is not registered (an unknown Kind participates in nothing).

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 instances 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_instance or delete_instance. 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_instance(scope, k, n, raw1) kernel.write_instance(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_instance async

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

Pure preview — returns target, serialized files, exists_already.

Writes nothing. exists_already is a UI hint so callers can render "create" vs "overwrite" affordances — but it is a real Path.exists() probe at <base_dir>/<scope>/<container>/<name>, so an unguarded traversing name would answer "does this arbitrary path exist?" and would render a target outside the store that write_instance would then refuse. Preview is the dry run of the write; it refuses what the write refuses (InvalidInstanceName / InvalidScopeName).

write_instance async

write_instance(
    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",
    if_absent: bool = False,
    if_match: str | None = None
) -> str | None

Persist an instance through the registered WritableSourcePort.

if_absent=True requests an ATOMIC CREATE: the write claims the name or raises :class:dna.kernel.errors.InstanceNameTaken. Requires an adapter that declares the if_absent write kwarg; one that does not gets a :class:NotImplementedError rather than a silently ordinary upsert, because a caller asking for the guarantee must not be told it got one it did not.

if_match requests a GUARDED UPDATE (i-083), held to that same standard: the write proceeds only if the STORED instance's spec still hashes to the token (:func:dna.kernel.etag.spec_etag — the same etag :func:~dna.application.instances.get_instance_impl hands back), else :class:dna.kernel.errors.StaleInstanceWrite, and nothing is written. An adapter that does not declare the kwarg gets a :class:NotImplementedError, never an unguarded upsert. Combining it with if_absent is a ValueError — the two assert opposite things.

It lives HERE rather than in the read-modify-write callers that want it, and the reason is what the guard must compare against. Those callers read through :meth:get_instance, served by a 60-second granular cache that only the WRITING replica invalidates — so a reviewer's read on replica B still answers v1 after an author's edit on replica A, and an application-level "re-read and compare" fetches v1 from that same cache, matches v1 against v1 and lets the clobber through. Pushed down to the adapter, the comparison is against the STORE: the row the SQL adapter reads inside the write transaction, or the bytes the filesystem adapter reads off disk.

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_instance 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. InvalidInstanceName — name is not a single, safe path component. InvalidScopeName — scope is not a single, safe path component. LayerPolicyViolationError — declared policy forbids the write. ValueError — invalidate_mode not in {scope, doc, none}; or if_absent and if_match passed together. KindRetiredError — Kind is in _REMOVED_KINDS (writes blocked). InstanceNameTaken — if_absent lost the race (the name is taken). StaleInstanceWrite — if_match lost the race (the instance moved).

delete_instance async

delete_instance(
    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 an instance through the registered WritableSourcePort.

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

Raises InvalidInstanceName / InvalidScopeName on the same path-component rule as write_instance, and for a sharper reason: the filesystem adapter unlinks — or rmtrees, for a bundle — <scope_dir>/<container>/<name>, so a traversing name here removes a file outside the store instead of merely creating one.

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).

unregister_kind

unregister_kind(
    api_version: str, kind: str, *, scope: str | None = None
) -> "KindPort | None"

Drop a registered Kind by its (api_version, kind) key, returning the removed port (None if it was never registered).

The counterpart to :meth:kind that the per-scope KindDefinition funnel always assumed existed and never had (i-080 item 3): without it, creating a tenant Kind was hot but EDITING one required restarting the process. See KindRegistry.unregister_kind for what goes with the port (its auto-synthesized generic reader/writer).

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, *, scope: str | None = None
) -> 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, *, scope: str | None = None
)

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.

run_manifest_activators

run_manifest_activators(mi: Any) -> None

Let every loaded extension activate its OWN instances on mi.

The generic half of what used to be ManifestInstance.apply_hooks. The kernel knows there is a step called "activation"; it does not know that Hook or SafetyPolicy exist — the extensions that register those Kinds implement :class:~dna.kernel.protocols.ManifestActivator and read their own schemas (i-112, board dna).

Feature-tested via hasattr for the same reason as list_templates: an extension that activates nothing (most of them) must keep satisfying Extension unchanged.

A misbehaving activator is warned about and skipped rather than allowed to take the whole scope down — the same fail-soft list_templates uses, and the same one the old inline code already applied to a Hook whose script body failed to compile. Activation decorates a manifest that is already valid without it.

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.compose.templates.materialize.

container_for_kind

container_for_kind(
    kind_name: str,
    *,
    api_version: str | None = None,
    scope: str | None = None
) -> "str | None"

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

api_version resolves the Kind EXACTLY (i-195 / i-080). Two Kinds may share a bare name — two workspaces each declaring Deal in their own namespace is the whole point of namespacing — and the bare lookup then picks ONE of them, which on a storage path means one workspace's instances are written into the directory of the other's Kind. Every caller holding the instance (which carries its own apiVersion) should pass it.

storage_for_kind

storage_for_kind(
    kind_name: str,
    *,
    api_version: str | None = None,
    scope: str | None = None
) -> "StorageDescriptor | None"

Return the StorageDescriptor for a kind, or None. Delegates to self._kindreg. See :meth:container_for_kind for why a caller with the instance in hand must pass api_version — and scope, since a store-loaded Kind routes storage only for its own scope (i-081).

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. - InvalidInstanceName — name is not a single, safe path component. - InvalidScopeName — scope is not a single, safe path component.

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_instance first). The doc owns the tenant identity — passing the same tenant here keeps the bundle row's tenant column aligned with the dna_instances row, so subsequent delete_instance 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).

list_bundle_entries

list_bundle_entries(
    scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None,
    only_tenant: bool = False
) -> list[str]

s-strain-bundle-fork B1 — list every entry path of a bundle.

Generic per-tenant primitive (routed by kind/container — never Skill-specific): default composes the tenant overlay ∪ the base layer (tenant rows shadow base by path). only_tenant=True returns just the tenant's OWN override rows (used to answer "what did this tenant fork?"). Sorted, deduped. Empty list when the bundle is absent.

Raises: - ValueError if the kind is not registered. - NotImplementedError if the source adapter doesn't implement BundleEntryReadable.list_bundle_entries. - InvalidInstanceName — name is not a single, safe path component. - InvalidScopeName — scope is not a single, safe path component.

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

list_bundle_entries_async async

list_bundle_entries_async(
    scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None,
    only_tenant: bool = False
) -> list[str]

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

delete_bundle_entry

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

s-strain-bundle-fork B1 — delete ONE bundle entry row for tenant (base sentinel "" when None).

Generic per-tenant primitive: reverting a tenant fork deletes the tenant-scoped row/file so fetch_bundle_entry composes through to the base layer again. Returns True if a row/file existed.

Raises: - ValueError if the kind is not registered. - NotImplementedError if the source adapter doesn't implement BundleEntryWritable.delete_bundle_entry.

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

delete_bundle_entry_async async

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

Async variant of delete_bundle_entry. 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_instance — 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, *, scope: str | None = None
) -> "str | None"

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

serialize_instance

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

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

Raises InvalidInstanceName / InvalidScopeName: it writes no bytes itself, but every relativePath it returns is BUILT from name (<container>/<name>.yaml, <container>/<name>/<entry>), and the whole point of the payload is that a caller writes those paths out. Handing back a relative path that traverses would move the escape one frame up the stack instead of closing it.

Which is what it did. Guarding scope and name made the CLAIM above true only for the half of the path they build: the <entry> half comes from instance CONTENT, and with an Agent or Skill carrying root_files this returned ['skills/x/SKILL.md', 'skills/x/../../../etc/cron.d/pwn'] — a traversing relativePath, handed to a caller whose job is to write it. The property is now enforced rather than asserted: every returned entry path is validated (InvalidBundleEntry), so the payload cannot describe a file outside the instance's own directory.

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_instances async

list_instances(
    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_instance via _invalidate_granular_cache. Single- flight via lock — N requests concorrentes na mesma key compartilham a fetch.

get_instance async

get_instance(
    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.

i-085 — an instance whose Kind was REVOKED comes back MARKED (status.valid == false), never as an error and never missing. The instance did nothing wrong; the workspace changed its mind about the Kind, and refusing the read would destroy the ability to audit what existed. See :func:~dna.kernel.validity.mark_invalid.

get_instance_local async

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

get_instance WITHOUT the parent-scope fallback — this scope only.

It is the same read get_instance performs FIRST, so calling it straight after one is served by the granular cache rather than by a second trip to the store.

It exists to answer a question get_instance deliberately hides: a instance came back, but from WHERE? Scope inheritance is the DEFAULT (_DenylistInheritable), so a declared reference can legitimately resolve in a parent scope, and the edge producer must be able to tell an intra-scope relation from an inherited one. Recording every hit as local would assert relations that do not exist inside the scope — the kind of quiet falsehood the graph exists to expose, not to commit.

resolve_instance_id async

resolve_instance_id(
    prefix: str,
    *,
    scope: str | None = None,
    tenant: str | None = None
) -> "Any"

Expand a short metadata.id prefix to the ONE instance it names (i-114) — the git / jujutsu / git-bug move, refusal included.

Returns a :class:~dna.kernel.identity.InstanceRef. Raises UnknownInstanceId when nothing matches, AmbiguousInstanceId when more than one does, and PrefixTooShort when the query is too short to be a question at all.

Ambiguity is a refusal, and that is the point of the whole feature. An id that silently resolved to the wrong instance reads exactly like one that resolved to the right one — no error, no stack trace, nothing in the diff. The store finds candidates; this method never picks among them, and neither does the store.

InstanceIdLookupUnsupported when the wired source cannot search by id. Deliberately not an empty result: "this adapter cannot answer" and "no such instance" are different facts, and fail-open in silence is this house's signature defect.

find_instance_by_key async

find_instance_by_key(
    scope: str,
    kind: str,
    key: str,
    value: str,
    *,
    tenant: str | None = None
) -> "tuple[dict[str, Any], str] | None"

The ONE instance of kind whose spec[key] is value, and the scope it was found in — or None (fatia 5).

What a relation declared by: workspace_id needs in order to be FOLLOWED. The store finds candidates; this method never picks among them, exactly as :meth:resolve_instance_id never picks among prefix matches, and for the same reason: a lookup that silently resolved to the wrong instance reads exactly like one that resolved to the right one.

It walks the SAME scope chain get_instance walks, and that is not a nicety. Three of the five Kinds addressed by key in this registry — PricingPlan, ModelProfile, Role — are registry Kinds read from every scope by inheritance. A by-key resolver that looked only in the writer's own scope would call every one of those references dangling, and the graph would fill up with breakage it had invented itself. Two addressings of one relation must not disagree about WHERE to look; they differ only in WHAT to match.

Raises AmbiguousInstanceKey when two instances in one layer carry the key, and KeyLookupUnsupported when the wired source cannot ask at all. Deliberately None for neither: "I cannot look", "two answers" and "no such instance" are three different facts, and only the third is the one None states.

graph_refs async

graph_refs(
    scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None,
    direction: str = "in",
    depth: int | None = None,
    as_of: str | None = None
)

"What points at this instance?" — the derived reference graph.

Thin facade over :func:dna.kernel.query.graph.traverse; the policy (depth ceiling, the unsupported refusal, the stop reason) lives there. Raises GraphUnsupported on a source that keeps no edges — never an empty list, which would read as "nothing points at it".

as_of (normalized ISO-8601 UTC) asks the SAME question at a past TRANSACTION instant. It is the fourth COORDINATE of one question — from where, which way, how far, when — and not a filter that composes; the graph module's docstring carries the measurement that decided how it is answered (re-derivation from dna_versions, because dna_edges is REPLACED on every write and therefore keeps no history at all). AsOfUnsupported on a store without version history, AsOfTruncated when the anchor's history was pruned past the instant, LookupError when it did not exist yet.

resolve_instance async

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

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

Returns ResolvedInstance 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 ResolvedInstance with provenance + is_inherited.

Cache: layer-level via _granular_doc_cached (same TTL/bound as get_instance). 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},
    "Skill":  {"local": 0, "inherited": 6,  "total": 6},
    ...
  },
}

WHICH Kinds are counted: the ones declaring composition.platform-default — the Kinds that ship defaults in _lib for a scope to inherit and override, which is exactly what a local-vs-inherited count is a report about (i-107). It used to iterate the literal DEFAULT_INHERITABLE_KINDS_V1, three of whose eight names were Kinds nothing registers — three wasted queries per call, forever, and no way for a tenant Kind to appear in its own sidebar.

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). The derived set is five Kinds today against the old eight, so the swap costs nothing — and it is bounded by DECLARATION rather than by registry size, which is the property that keeps it cheap as the registry grows past 89.

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_instance async

personalize_instance(
    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: ResolvedInstance 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.

i-085 — rows of a REVOKED Kind are yielded MARKED (status.valid == false), never dropped. See :meth:_mark_validity for why hiding them is not something this path can express honestly.

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,
    min_similarity: float | None = None,
    name_prefix: 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).

min_similarity (i-103) is the CALLER's relevance floor over the dense plane's raw cosine — never a default, because no cutoff separates relevant from irrelevant on a measured corpus. See :mod:dna.kernel.query.relevance.

query_list_sync

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

Sync wrapper around query returning parsed Instance 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_instance_sync

get_instance_sync(
    scope: str,
    kind: str,
    name: str,
    *,
    tenant: str | None = None
) -> Instance | None

Sync wrapper around get_instance returning a parsed Instance (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).

tier async

tier(tier_id_or_alias: str) -> dict | None

Resolve a Tier (DNA Cloud pricing plan) from the _lib registry by tier_id, then by aliases[]. Returns the RAW DICT row (callers read tier["spec"][...]) or None. _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator — mirrors model_profile. The quota enforcer reads the caps from here, never hardcodes them.

account_plan async

account_plan(account_id: str) -> dict | None

Resolve an AccountPlan (an ACCOUNT→Tier assignment) from the _lib registry by spec.account_id. Returns the RAW DICT row (callers read plan["spec"]["tier_id"]) or None when no assignment exists. _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator — the billing→enforcement bridge: the subscription belongs to the BILLING ACCOUNT, so ONE plan covers every workspace the account owns. dna-cloud's Stripe webhook writes the doc; the SDK only reads it. A blank account_id resolves to None (fail-closed).

account_for_workspace async

account_for_workspace(workspace_id: str) -> str | None

The BILLING ACCOUNT id a workspace belongs to — Workspace.account_id — or None when the workspace is unknown or carries no account.

The first half of the enforcement resolution workspace → account_id → AccountPlan. FAIL-CLOSED by omission: None means the quota guard finds no plan and falls to the Free floor — never another account's tier and never a paid default. _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator.

workspace_memberships async

workspace_memberships() -> list[dict]

List every WorkspaceMembership grant from the _lib registry (ADR "Model B"). Returns the RAW DICT rows (unfiltered — the auth→workspace resolver filters by the verified identity in pure core); [] means the source never opted into workspaces (the auth bridge then falls back to the legacy tid tenancy). _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator.

workspaces async

workspaces() -> list[dict]

List every Workspace doc (the tenancy ROOTS) from the _lib registry. Returns the RAW DICT rows, UNFILTERED — an inventory read, not an authorization surface: the caller-facing GET /v1/workspaces filters it by the verified identity's ACTIVE memberships in pure core. _lib-direct + fail-soft. Thin facade over the RegistryAccessor collaborator — mirrors workspace_memberships.

kind_namespaces async

kind_namespaces() -> list[dict]

List every KindNamespace claim (namespace → owning workspace, i-080) from the _lib registry. Returns the RAW DICT rows; the ownership verdict is computed in pure core (dna.kernel.kinds.namespaces.owner_of).

_lib-direct and — uniquely on this facade — NOT fail-soft: an unreadable authorization registry is not an empty one, and the write gate needs to tell the two apart. Thin facade over the RegistryAccessor collaborator.

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.

from_config classmethod

from_config(path: str | None = None) -> 'Kernel'

Boot a fully-wired Kernel from a dna.config.yaml (declarative port wiring — s-dx-kernel-from-config).

The config selects the source (file:// / sqlite:// / postgresql://) and, optionally, the search + embedding providers; every port is resolved to its adapter and wired. With NO config present (and no path given) the behavior is unchanged — a filesystem .dna source, exactly like the bare default.

Returns a wired Kernel; call .instance(scope) for the ManifestInstance (Runtime.from_config(...).manifest(scope) for the Runtime vocabulary). cls is threaded through like auto/quick so Runtime.from_config() returns a Runtime.

This is a boot-time factory: SQL sources run their migrations here via a short-lived event loop, so call it during startup, not from inside a running loop.

Runtime

dna.kernel.boot.runtime.Runtime

Runtime(
    *,
    tenant: str | None = None,
    allow_personal: bool = False
)

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).