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 ¶
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
¶
The KernelEventBus registered via event_bus(), or None.
active_source
property
¶
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 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
¶
WriterPorts registered via writer(w). Read-only view — mutating the returned tuple has no effect on the Kernel's internal list.
active_readers
property
¶
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
¶
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
¶
Output dimensionality of the active embedding provider.
embedding_model_id
property
¶
Identity of the active embedding space (vectors from different
model_ids are NOT comparable).
register_main_loop ¶
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 ¶
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 ¶
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 ¶
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 ¶
All registered KindPorts. Order matches registration. scope
narrows to the Kinds that govern that scope (i-081).
embeddable_kinds ¶
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 ¶
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 ¶
The registered ports declaring trait, in registration order.
traits_of ¶
Every trait declared by kind — frozenset() when the name is
not registered (an unknown Kind participates in nothing).
use ¶
Register middleware on a hook point (e.g., 'pre_build_prompt').
on ¶
Register event subscriber (e.g., 'post_save').
on_veto ¶
Register a veto listener (e.g., 'pre_save') — raising vetoes the
operation. See HookRegistry.on_veto for priority/key semantics.
source ¶
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 ¶
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 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 a holder. Idempotent — unregistering an unknown holder is silently ignored.
event_bus ¶
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 ¶
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 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
¶
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 ¶
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
¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
get_tool ¶
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 ¶
Reverse-build {group: [tool_names...]} from the registry.
describe_kind ¶
Return a summary dict for a registered kind, including resolved docs.
Delegates to self._kindreg.
composition_profile ¶
Register a composition profile that declares how an orchestrator kind connects to other kinds.
resolve_dep_filter_target ¶
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 ¶
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 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 ¶
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 ¶
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 ¶
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 kind → container 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 ¶
Return the kind name whose StorageDescriptor.container matches.
Delegates to self._kindreg (None for empty/unregistered).
serialize_instance ¶
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
¶
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 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
¶
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 ¶
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 ¶
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 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 ¶
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 on an existing MI (sync wrapper). Delegates to
self._builder (s-kernel-decompose-god-object).
resolve_layers_async
async
¶
Async-native layer resolver — MI.resolve_async() delegates here, then
through self._builder (s-kernel-decompose-god-object).
model_profile
async
¶
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
¶
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
¶
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
¶
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
¶
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
¶
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
¶
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
¶
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
¶
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-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
¶
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
¶
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 ¶
Bases: Kernel
Runtime that integrates any agent configuration standard.