5. Extensions
In plain words: Design rule 05: Extensions
Decides how features plug into LychD: either as trusted in-process Python contributions accepted by a named owner, or as separate external services. Defines the vocabulary of Plug-in Areas (Extension Domains), packages, contributions and providers.
- 1In-process extensions share the server's memory and can crash it, so only trusted, explicitly selected code gets that path.
- 2A contribution is a typed addition accepted by one explicit owner (a core office or a Plug-in Area), not free-form app mutation.
- 3External services give process isolation and their own lifecycle; MPL-2.0 lets private extensions stay private.
Partial Extension activation works; the general service surface and package API are designed only.
Words on this page, decoded click one for its card
Context and Problem Statement
LychD admits native Python where it needs its speed and reach, while keeping authority with the explicit Core office or Extension DomainPlug-in Area that receives a Contribution. The architecture must also leave a real boundary for separate engines, dependencies, images, and lifecycles. The operational journey is owned by the Extensions Sepulcher; this leaf states the constitutional boundary.
Decision Outcome
An extension can join LychD as an admitted, protocol-bound native contribution or as an external service. Native Python provides direct access where the receiving owner accepts that trust; external protocols provide isolation or independent deployment where those are required. Making every extension an external service would impose latency and deployment costs. Sandbox scripting remains a future containment option, with no second language or runtime promised today.
The Extension Protocol joins local-package admission and external-provider integration without conflating their trust or lifecycle boundaries. Semantic contract and profile identities remain separate from their concrete Provider implementations. A future Forge lock lifecycle must pin foreign source and physical inputs; selected import shims alone are not reproducibility evidence.
AnA Extension DomainPlug-in Area is one of the fifteen stable user-facing jurisdictions. An extension package is selected code. A Contribution is a typed addition accepted by one explicit receiving owner, which may be a Core office or ana Extension DomainPlug-in Area. A Provider is a concrete engine or service behind a typed contract. Domain presence proves neither a package nor delivery; activation is of a concrete package or declared instance, never an abstract Domain. CompositionsApps use Domain contracts without becoming their owners. Packages may contribute across several Core offices and Extension DomainsPlug-in Areas, and one receiving owner may admit many packages or Providers.
A Registrant is Core or one explicitly selected extension package performing registration. Its
host-assigned registrant_id (core, builtin:<activation-id>, or
crypt:<activation-id>) records registration provenance; it is not Provider identity and never
turns the Registrant into an engine or service Provider.
Do not compress authority owner, package provenance, Contribution kind, Provider or external attachment, profile, and maturity into one union category. State the exact applicable facets. Every operational reference resolves to its concrete Core office or Extension DomainPlug-in Area, Contribution, Provider or attachment, profile revision, and Registrant provenance as required by the contract.
Vocabulary Boundary: Domain Is Not Package
Core owns schemas, policies, lifecycle, and host effects. A package can add only the explicit shapes accepted by a receiving owner. Keeping Domain and package separate therefore lets each contribution state its provenance, trust, evolution, and configuration without making import location stand in for those decisions.
1. The Federation Strategy
The host federates selected in-process Python packages and uses external protocols when needed. It initializes the extension context once at boot, from configuration, only for selected built-in and CryptData Folder packages. It discovers neither the environment nor entry points.
Built-ins live under src/lychd/extensions/builtin/; CryptData Folder packages are explicitly selected local
register.py shims. Installed never means active. The selector is not the future Forge manifest,
which must eventually pin source, dependencies, and physical substrate.
[extensions]
builtins = []
crypt = ["my-private-organ"]
lychd init may show commented choices, but the default is inert. A selected package is
imported and calls register(context). Enabling it permits schemas, hydrators, and store
registration; it does not start an instance. CodexSettings Folder receives the resulting schema list, not a
package scan. Built-in dependencies are explicit catalogue edges: selecting a concrete AnimatorModel Runtime
runtime assembles the shared animator base once, under its own provenance, before the runtime.
CryptData Folder activation resolves only that exact register.py; it performs no package or entry-point
discovery. The shim is loaded in an injectively named synthetic package so ordinary relative
sibling imports work without making neighboring CryptData Folder packages active.
If import or register() fails, the manager removes that synthetic package namespace before
surfacing the error, so a repaired retry cannot inherit half-imported module state.
Activation ids must already be canonical POSIX-relative paths: aliases, traversal, absolute paths,
empty segments, and control characters are refused before import.
One process, one extension generation
The selected package set is assembled as one process generation. Registration mutates only a fresh assembly context; successful assembly seals membership before runtime consumers receive their projections. LychD does not hot-load, hot-replace, or unload executable extension code inside a live VesselEngine Room. A change to code, dependencies, package selection, or contribution contracts is an Evolution of the body: prepare an inactive candidate, settle admitted work, replace the VesselEngine Room generation, and reconstruct volatile state from validated configuration and durable truth.
Process replacement keeps one executable generation coherent. A fine-grained lifecycle can reverse registrations and resources only when it mediates them: references, closures, background tasks, caches, foreign-library state, and already-emitted filesystem, database, or network effects can escape its ledger. Removing a module therefore cannot prove that old and new memory no longer coexist. LychD treats process memory as disposable instead of requiring perfect effect capture throughout the system. Ordinary shutdown must still quiesce owned workers and resources. That teardown hygiene neither authorizes live code replacement nor proves external effects rolled back.
A future declarative catalogue generation may activate without VesselEngine Room replacement only when it introduces no new executable implementation and its owner supplies atomic durable generation and pinning semantics. Otherwise activation follows SDLC, Packaging, and Evolution into a new process generation.
2. The Registration Surface (The Extension Context)
The context has shaped stores for runes, soulstones, portals, transmutation, and
delegated_runtimes. New contribution kinds need an explicit owner. Patterns, CompositionsApps,
status, routes, tools, workloads, operations, and migrations are not implied merely because a
package registers. A minimal contribution is explicit:
context.runes.add_schema(RuneConfig)
The manager owns the root context and passes each Registrant a registrant-bound
ExtensionRegistrationContext. User activation IDs remain the Settings and filesystem selectors;
audit provenance uses disjoint trust-domain identities: core, builtin:<activation-id>, and
crypt:<activation-id>. Its shaped store facades capture one fixed registrant_id and do not
expose the root provenance mutator; even a retained facade cannot inherit a later Registrant's
identity. Stores retain that provenance, reject another Registrant's replay even when the
Python value is equal, and seal membership after the one assembly pass. AssembledExtensions
exposes read projections plus the sealed root context; retained registration methods remain
present for assembly compatibility but reject every post-assembly write. It is not a live registry
that arbitrary runtime code may extend. This seal does not recursively freeze trusted contributed
Python objects; each Contribution contract must provide its own immutability or defensive-copy
boundary.
The v1 AnimatorModel Runtime catalogue retains these registration records through hydration. Capability
specifications, grants, OrchestratorStage Manager status, and NexusModel Status Board response rows carry the host-assigned
registrant_id separately from runtime and Provider identity. Adapter-supplied attribution cannot
override it. Direct adapter injection without a registration record remains explicitly
unattributed (None); it does not acquire Core provenance. This metadata changes neither the
persisted capability key nor eligibility or effect authority.
The accepted general-service capability surface is Designed and is not present in that context. It requires separately owned and sealed contribution stores for semantic interface revisions, immutable profile revisions/digests, Connector dialect-driver revisions, evidence bindings, and runtime/PortalCloud Model activation definitions. Registration of one kind grants none of the others:
- an interface defines semantic request/result, operations, typed facets, and failure meaning;
- a profile closes exact model/tool/graph/workflow inputs, licenses, formats, languages, limits, resources, and evidence references;
- a dialect driver speaks one exact protocol subset and owns no semantic eligibility;
- an activation definition binds an admitted profile and driver to one RuneService Config File schema and lifecycle; and
- an evidence binding points to producer-attributed results and an admission decision by the target contract owner.
Every registered identity includes a safe stable id plus immutable revision or digest, one explicit receiving owner, its Contribution kind, and Registrant provenance. A Provider-facing identity separately pins its concrete Provider. Cross-store references are resolved and validated as one staged assembly before membership seals. Registering a RuneService Config File schema, Python client, URL, or familiar "OpenAI-compatible" label cannot synthesize any missing Contribution.
A versioned application deployment profile is another receiving-owner contract, not raw unit text
or authority inherited from aan CompositionApp name. The future sealed deployment_services surface
may admit exact service-role contributions from Core or ana Extension DomainPlug-in Area; Containers owns the
ApplicationDeploymentManifest@1 physical schema and compiler, Configuration owns selection and
generation assembly, and the application profile owns which registered roles its workload needs.
A contribution pins its owner, implementation revision, IPC contract, lifecycle, secret, network,
mount, resource, and recovery requirements. Registration starts no service and cannot add an
undeclared port, command, mount, network, credential, migration, or dependency during compilation.
This surface is Designed and absent from the current ExtensionRegistrationContext.
Future SpellStep and ScrollWorkflow Version contribution preserves that boundary. Spellweaver-owned stores may admit portable SpellStep contracts and ScrollWorkflow Version declarations; executable implementation and adapter stores remain separately owned by Extension and effect Domains. Contract publication, implementation registration, ScrollWorkflow Version publication, and activation are distinct acts. None is implied by the current registration context or by the presence of an equally named package.
Web-acquisition providers preserve the same rule. A package may register ana AnimatorModel Runtime definition
for a search or browser service, but a SoulstoneDefinition, PortalDefinition, or raw
ToolConnector is not a ScoutWeb Fetcher provider registration and grants no Search, Fetch, Crawl, Render,
or Extract authority. The future provider contribution surface must be explicitly Scout-owned,
effect-specific, provenance-preserving, and sealed with the other registration stores. Until that
surface exists, provider-facing toolsets remain incapable of constituting a delivered ScoutWeb Fetcher path.
3. Contributions as Organs
RunesService Config Files, SoulstonesLocal Models, PortalsCloud Models, and Transmutation admit active schemas and definitions. A
SoulstoneDefinition couples its schema to an Animator-owned runtime adapter and registers that
schema into the shared rune store. Runtime schema discovery imports only the selected package; a
loader is the singular TOML parser and validator, not a ledger. __subclasses__ may audit an
already-loaded process but cannot establish registration or change whether an admitted schema is a
file-owning leaf. Branch ownership is computed from the exact admitted schema generation. Exact
repeat registration is idempotent
only for the same Registrant. RuneService Config File schema admission also reserves its exact filesystem anchor; a
different schema cannot claim the same relative_path. SoulstoneLocal Model registration identity is the
runtime name, RuneService Config File schema, and adapter type. The same runtime or schema with another owner fails
closed instead of silently preserving first registration. PortalCloud Model schemas likewise have one exact
factory owner. The VesselEngine Room composition root passes the definitions, not an ordered list of anonymous callables;
runtime dispatch looks up the PortalCloud Model's exact RuneService Config File schema. A broad factory cannot claim another
package's declaration, and a factory is total for every value its schema admits. Each definition
also owns its optional typed probe strategy. A PortalCloud Model that requests a live probe without that exact
strategy fails closed; connector shape or a coincidental method name never selects an egress
protocol. The factory protocol is total: schema validation must refuse values its exact owner
cannot construct.
The RuneService Config File registry deep-copies admitted schema metadata and every returned snapshot, including nested mutable values. Membership sealing and snapshot isolation are separate laws: trusted contributors may still own mutable runtime objects, but callers cannot mutate canonical RuneService Config File metadata through a value returned by the registry.
Every selected register(context) shim is a synchronous boot hook and must return None.
Awaitables and other return values fail assembly; an async function is never silently admitted as
an unexecuted registration. CryptData Folder failure clears that activation's synthetic import generation.
Live AnimatorModel Runtime RuneService Config File, group, and capability projections add their own defensive-copy boundary before
values cross into orchestration, policy, or adapter code.
The built-in delegation extension has one no-network reference adapter and fail-closed
declarations for provider-backed candidates. A declaration describes immutable transport,
delivery, security, and limitation; it never executes a matching binary by itself. Registration
supplies neither persistence nor infrastructure authority: those need accepted ordering, recovery,
export, deletion, and uninstall contracts.
4. Substrate Injections
Operating-system libraries, binaries, images, services, resources, licenses, and wider substrate requirements enter through the Extension Protocol and Forge manifest, not the boot context. Forge is where platform validation and operator consent can be made explicit.
5. Runtime Schema Discovery
A schema makes TOML loadable; a Domain's runtime definition and hydrator make the configured
capability usable. CodexSettings Folder validates the configuration, an adapter builds state, and the resulting
LychD-facing Runic[T] handle retains its .rune provenance. A foreign object becomes Runic
only through such a wrapper. Foreign internals remain sovereign even when a system uses rune
machinery: translated configuration is the stable boundary.
6. The MPL 2.0 Shield (Private Extensions)
MPL 2.0's file-level terms allow a proprietary separate extension to link with LychD. That is not a trust decision. A private in-process package can access process memory, so admission remains an intentional operator and owner choice.
7. Extension Compatibility Tiers
The Built-in Direct Path
src/lychd/extensions/builtin/ is core-owned. It may use internals and ABCs because it releases
atomically with the core and is selected by convention.
The Private Coupled Path
CryptData Folder packages may live outside the tree while importing internals and RuneConfig. This is a
Magus-local, refactor-risking path; Assimilation may repair it, but no compatibility guarantee is
made.
The Independent Extension Package Path (v1+)
An independently released extension package is deferred until a versioned public API, conformance tests, and Forge
packaging exist. No SDK is created now and no independent compatibility promise is implied.
Foreign agent frameworks are not first-class in-process runtimes: use an external-service
AnimatorModel Runtime, A2AAgent-to-Agent Line Emissary, or DelegatedAgentNode adapter, or assimilate their useful patterns.
| Property | Built-in Direct | Private Coupled | Future Independent Package |
|---|---|---|---|
| Location | src/lychd/extensions/builtin/ |
Magus-owned CryptData Folder space | Forge-managed distribution |
| Coupling | Core internals and subclasses | Internals by choice | Versioned public API only |
| Loader | Selected import + register(context) |
Selected shim + register(context) |
Deferred, manifest-gated |
| Release cycle | Atomic with Core | Operator-owned | Independent |
| Stability | Core-maintained | Local repair only | Not promised until productized |
Rune And Runtime Boundary
Schemas and wrappers separate SoulstoneDefinition from its runtime adapter. Provenance travels
with a rune, not an assumption that foreign objects share core identity.
Delegated-Agent Runtime Adapters
An adapter has a schema and declared limits, not a node class. Its exact non-shell builder covers
one audited runtime/version range; it starts, polls or streams, cancels the process tree, and
settles results. Untrusted provider output becomes an AgentJob result or event, subject to
usage/rate-limit, reset, health, and provenance observations. It receives a secret-free grant and
Coffin—not graph access, persistence,
quotas, credentials, promotion, or reanimation—and may emit bounded redacted JSONL, never a hidden
graph or chain-of-thought record. A new provider is an adapter plus configuration contribution, not
a node class or second orchestration kernel. A Provider aggregator is admitted through a
PortalCloud Model/provider gate and an allowlist.
Cross-Language Organs
There is no stable cross-language ABI. Coupled code may be composed and repaired through Forge;
the future public API is the only route to independence. External services provide the present
true separation. LychD performs no blind .so scan: binary loading requires the Forge manifest,
platform validation, and explicit operator consent before runtime import.
Consequences
Admitting in-process code means trusting it with the daemon's memory and stability: it can crash or corrupt the process. Only trusted, admitted source receives that path, with explicit selection and contracts. External protocols provide process isolation and independent lifecycle. A future public extension-package API is still required for in-process refactor independence; a package name grants none of these properties.