On this page

SpellbookConfiguration

Use md.SpellbookConfiguration from the public package namespace.

Implementation source

class SpellbookConfiguration(aether_frame: str = 'default')[source]

Mutable build-time configuration surface for one spellbook/runtime context.

SpellbookConfiguration is the central staging object for one Spellbook's rich local runtime behaviour. It owns the typed property map, idempotent keys, and the per-spellbook system hook registry that later runtime systems consume.

It governs:

  • local Spellbook/runtime behaviour

  • disposal and phase-scheduler tuning

  • hook registration for Meld / Conduit / Link / Contract events

  • initial Bind callbacks, copied into each Book's independent registry

Contract:

  • Properties are mutable only until the configuration is frozen.

  • Idempotent keys may be set once and then become immutable even before freeze.

  • Validation is explicit and freeze/finalize enforce it.

  • Thread-safe operations are serialized with the instance RLock.

Registration:

MELDER KERNEL - guarded. Created or adopted during Spellbook.__init__; users configure it directly, which is its whole purpose.

Subsystem Context:

The rich per-spellbook configuration, distinct from the NARROW AethericFrameConfiguration derived from it at conjure. It owns the typed property map, idempotent keys, and the system hook registry for Meld / Conduit / Link / Contract events.

System Context:

IDEMPOTENT KEYS are the unusual and important mechanism here: they may be set ONCE and then become immutable EVEN BEFORE FREEZE. That exists because some properties are adopted from a frame-shared configuration or established at first bind, and letting them be rewritten later would silently change the contract that earlier binds already committed to. Freeze is the second gate: configuration must be frozen before a Conduit can be created, so the conjure pipeline plans against values that cannot move underneath it. The crystallizer enforces the same discipline from the record side - a dynamic conjure over binds that ran while the configuration was still mutable is REFUSED, because a recorded world must never be born config-incoherent. Hooks living here rather than on the Spellbook is what makes them adoptable: a frame-shared configuration carries one hook registry for every book that adopts it.

AGENT_ACCESS: public

AGENT_PURPOSE:

access: public. Rich per-book configuration: typed properties, idempotent keys, and the Meld/Conduit/Link/Contract hook registry. Must be frozen before conjure. Idempotent keys lock after first set, even before freeze.

cleanup() → None[source]

Finalize the configuration and drop all owned registries.

This method sets both the cleaned and frozen flags.

Contract:

  • Idempotent and lock-guarded.

  • Clears property/type maps and hook registries.

  • Releases Bind seeds without changing Books already initialized from them or disposing the borrowed callback objects.

  • Prevents any future mutation or validation calls through check_cleaned().

Raises:

RuntimeError -- If the configuration is already cleaned.

Returns:

None.

property aether_frame: str

Return the name of the Aether frame this configuration was built for.

Contract:

  • The aether_frame given at construction ("default" when omitted); it never changes.

  • When a Spellbook is handed a configuration and its frame has no shared configuration to adopt, it refuses one whose aether_frame is not the Spellbook's own frame, so this is the value to compare before handing a configuration over.

Threading:

Unsynchronized read of a value fixed at construction.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Returns:

The frame name.

Return type:

str

Raises:

RuntimeError -- If the configuration has been cleaned.

property frozen: bool

Return whether this configuration has been frozen.

Contract:

  • False while properties and hooks can still change; True once freeze() (or finalize() / build()) has succeeded. A configuration must be frozen before a Conduit is conjured from it.

  • Reading it changes nothing: it never freezes or validates.

Threading:

Reads under self._lock, the lock freeze() holds while it sets the flag.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Returns:

True when frozen.

Return type:

bool

Raises:

RuntimeError -- If the configuration has been cleaned.

set_property(key: str, value: Any) → None[source]

Defines or overwrites a property in the configuration.

  • Idempotent properties (e.g., 'system_state') can only be set once before the configuration is cleaned.

  • Non-idempotent properties can be freely modified before the configuration is frozen.

Parameters:
  • key (str) -- The name of the property to set.

  • value (Any) -- The value of the property.

Raises:
  • RuntimeError -- If the configuration is cleaned or frozen.

  • RuntimeError -- If attempting to modify an idempotent property that is already set.

  • TypeError -- If key is not a string.

  • ValueError -- If local value validation fails.

Returns:

None.

clear_properties() → None[source]

Reset authored properties to the initial build-time state.

The disposal-priority flag returns to False; all other properties are removed. The configuration remains available for reassembly before freeze.

Raises:

RuntimeError -- If the configuration is cleaned or frozen.

Returns:

None.

freeze(origin_spellbook_id: str | None = None, origin_frame_name: str | None = None, origin_dynamic: bool | None = None, origin_bind_hook_names: Sequence[str] = ()) → None[source]

Freezes the configuration property system.

Once frozen, no properties, including non-idempotent ones, can be modified. Validation is performed automatically upon freezing.

Contract:

  • Freezing is this configuration's true activation, so it is the emission factor for the spellbook twin: when origin identity is supplied, the world is dynamic, and the crystallizer records, the twin spawns into the active persistence profile.

  • Standalone freezes (user finalize()/build() before any Spellbook adoption) carry no origin identity and emit nothing; the owning spellbook's conjure-time freeze supplies identity.

Parameters:
  • origin_spellbook_id -- Owning spellbook identity, supplied by the spellbook's conjure-time freeze path; None for standalone freezes.

  • origin_frame_name -- Owning frame name (the twin's parent edge).

  • origin_dynamic -- Conjure-time dynamic posture; the twin emits only when True (the recorded lane).

  • origin_bind_hook_names -- Value-only stage markers supplied by the owning Book. Bind runtime registries remain owned by Book/Bind; configured seeds are initialization inputs, not the Book's current hook state. Standalone callers omit this argument.

Raises:
  • RuntimeError -- If the configuration is cleaned.

  • ValueError -- If configuration validation fails prior to freezing (e.g., missing required properties).

Returns:

None.

validate() → bool[source]

Validates that all configuration properties are present, correctly typed, and semantically valid.

The validation pipeline is intentionally decomposed into small, focused helper methods to avoid an overly long and unmaintainable validate() method.

Contract:

  • Checks the configuration's own coherence. It does not consult any spellbook, so passing here does not guarantee a given spellbook will accept the configuration.

  • Raises on violation rather than returning False.

Threading:

State transitions are applied under the configuration lock.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the configuration has been cleaned.

Returns:

True when every required property is present and coherent. Raises

rather than returning False for a missing required property.

Return type:

bool

validate_enums() → bool[source]

Local rich configuration no longer owns enum-backed frame posture properties, so enum validation is a no-op.

Returns:

True if all enum values are valid.

Return type:

bool

Raises:
  • RuntimeError -- If the configuration is cleaned.

  • ValueError -- If a known enum property is set to an invalid type.

get_property(key: str) → Any[source]

Return one configuration property value.

Parameters:

key (str) -- The name of the property.

Returns:

The stored value (str, int, bool, Enum, etc.).

Return type:

Any

Raises:
  • RuntimeError -- If the configuration is cleaned.

  • KeyError -- If the property does not exist in the configuration.

Contract:

  • Returns the stored live value for the key.

  • Raises instead of silently defaulting when the key is missing.

has_property(key: str) → bool[source]

Return whether a configuration property is currently defined.

Parameters:

key (str) -- The property name to check.

Returns:

True if the property exists, False otherwise.

Return type:

bool

Raises:

RuntimeError -- If the configuration is cleaned.

load_default_dictionary() → None[source]

Load the standard default property set.

This method sets sensible defaults for local rich-config properties.

Contract:

  • Populates only missing local rich-config properties; existing explicit values are preserved.

Raises:

RuntimeError -- If the configuration is cleaned.

Returns:

None.

load_recorded_dictionary(recorded_properties: Dict[str, Any]) → Dict[str, List[str]][source]

Reload lane: apply one RECORDED property payload as configuration truth.

Purpose:

The restore/reload counterpart to load_default_dictionary. A sealed world must rebuild from its recorded values - never from present-day defaults, which drift - so this verb applies the recorded payload first and backfills ONLY required keys the record does not carry, reporting every deviation to the caller.

Contract:

  • Recorded values win: each recorded key routes through set_property for registration/lifecycle checks; type checking occurs during the final validation/freeze.

  • A recorded key the property system refuses (unknown key or lifecycle/write-once violation) is skipped and returned under "rejected" as "key: reason"; nothing is silently coerced. The per-key exception capture is documented best-effort collection, not error swallowing - every refusal is surfaced to the caller. Invalid registered value types fail at the final validation.

  • Required keys absent from the record backfill through load_default_dictionary (populate-missing-only semantics) and each backfilled key is returned under "backfilled"; nothing backfills silently.

  • An omitted disposal-priority flag still holding its eager False default is also reported as backfilled. An existing non-default True is preserved when the record has no replacement.

  • LOADS AND FREEZES in one motion: a reloaded configuration is sealed truth, so the verb validates and freezes internally before returning - callers never finalize a reload. The standalone freeze carries no origin identity (no twin emission); the owning spellbook's conjure-time freeze re-enters with identity and emits.

Parameters:

recorded_properties -- Property name -> recorded value mapping (one sealed, JSON-safe configuration_payload - the cached-item shape).

Returns:

{"rejected": ["key: reason", ...] for refused recorded

values, "backfilled": [key, ...] for schema-default fills}.

Return type:

Dict[str, List[str]]

Raises:
  • RuntimeError -- If the configuration is cleaned.

  • ValueError -- If the reloaded property set fails validation at the internal freeze.

add_bind_hooks(*, pre: Sequence[Callable[[Any], object]] | None = None, activation: Sequence[Callable[[Spell], object]] | None = None, post: Sequence[Callable[[Spell], object]] | None = None) → None[source]

Append initial registration callbacks for Books constructed from this config.

Contract:

Validates every callback before publishing any stage. Preserves order and duplicates. Each Book captures the immutable tuples once during construction; later configuration edits affect future Books only. Existing Books use their own add_bind_hooks/clear_bind_hooks methods. Callback objects are borrowed and never executed or disposed here.

Parameters:
  • pre -- Ordered checks receiving the binding input reference.

  • activation -- Ordered callbacks receiving the unpublished Spell.

  • post -- Ordered callbacks receiving the registered Spell.

Returns:

None. None or an empty sequence adds no callbacks.

Raises:
  • TypeError -- A stage is not iterable or contains a non-callable.

  • RuntimeError -- The configuration is frozen or cleaned.

Threading:

Tuple replacement and lifecycle checks use the configuration lock. No callback runs under that lock.

clear_bind_hooks() → None[source]

Clear initial Bind callbacks without changing any existing Book.

Contract:

Releases callback references without invoking them. An already-created Book retains its own immutable set. Empty defaults remain empty.

Returns:

None.

Raises:

RuntimeError -- The configuration is frozen or cleaned.

Threading:

Replacement and lifecycle checks use the configuration lock.

get_bind_hooks() → BindLifecycleHooks[source]

Return the immutable pre/activation/post seed set for Book initialization.

Contract:

Returns the current tuple without copying callback objects. Subsequent configuration or Book mutations replace tuples independently.

Returns:

Three ordered tuples, empty by default.

Return type:

BindLifecycleHooks

Raises:

RuntimeError -- The configuration is cleaned.

Threading:

Reads under the configuration lock; no callbacks are invoked.

with_bind_hooks(*, pre: Sequence[Callable[[Any], object]] | None = None, activation: Sequence[Callable[[Spell], object]] | None = None, post: Sequence[Callable[[Spell], object]] | None = None) → SpellbookConfiguration[source]

Append initial Bind callbacks and return this configuration for chaining.

Parameters:
  • pre -- Ordered callbacks receiving the original binding reference.

  • activation -- Ordered callbacks receiving the unpublished Spell.

  • post -- Ordered callbacks receiving the registered Spell.

Contract:

Delegates validation and atomic publication to add_bind_hooks; it neither executes callbacks nor updates already-created Books.

Returns:

This same instance.

Return type:

SpellbookConfiguration

Raises:
  • TypeError -- A supplied stage contains invalid callbacks.

  • RuntimeError -- The configuration is frozen or cleaned.

add_hook(spellbook_id: str | None, hook_name: str, hook: Callable[[...], Any]) → None[source]

Register a single system hook under this configuration for a specific Spellbook.

The registry is keyed as:

_hooks[spellbook_id][hook_name] -> list[callables]

Covered hook categories:

  • Meld pipeline hooks:

- "on_meld_pre_resolve"
- "on_meld_post_resolve"
  • Conduit lifecycle hooks:

- "on_conduit_pre_created"
- "on_conduit_post_created"
- "on_conduit_activated"
- "on_conduit_cleanup_start"
- "on_conduit_cleanup_complete"
  • Linking hooks:

- "on_conduit_post_link"
- "on_conduit_post_unlink"
  • Contract hooks:

- "on_contract_created"
- "on_contract_removed"
Parameters:
  • spellbook_id (Optional[str]) -- The ID of the Spellbook these hooks belong to. This allows dynamic environments to register hooks per-Spellbook and later pull the appropriate hook sets when instantiating Conduits. None supplies a default for future Books. An explicit Book's list replaces the default list for the same event.

  • hook_name (str) -- The canonical hook name to register. Must be one of: attr:_ALLOWED_HOOKS.

  • hook (Callable[..., Any]) -- A callable to be invoked when the corresponding hook event fires.

Raises:
  • RuntimeError -- If the configuration is cleaned or frozen.

  • ValueError -- If hook_name is unknown.

  • TypeError -- If hook is not callable.

Returns:

None.

add_hooks(spellbook_id: str | None = None, **hooks: Any) → None[source]

Register system hooks for one Spellbook or for future Books by default.

Contract:

Validates the complete batch before adding any callback. Omitted spellbook_id supplies defaults; explicit Book events replace the corresponding default event lists during setup. No live propagation.

Each keyword argument maps a hook name to either:

  • A single callable, or

  • An iterable of callables.

The internal registry shape is:

_hooks[spellbook_id][hook_name] -> list[callables]

Example

cfg.add_hooks("spellbook-123",
    on_meld_pre_resolve=trace_meld_enter,
    on_conduit_cleanup_complete=[cleanup_fn_1, cleanup_fn_2],
    on_contract_created=contract_observer,
)
Parameters:
  • spellbook_id (Optional[str]) -- Book ID, or None for default hooks when the future ID is unknown.

  • **hooks -- Mapping of hook name -> callable or iterable[callable].

Raises:
  • RuntimeError -- If the configuration is cleaned or frozen.

  • ValueError -- If any hook name is unknown.

  • TypeError -- If any value is not a callable or an iterable of callables.

Returns:

None.

get_conduit_hooks(spellbook_id: str) → Dict[str, list[Callable[[...], Any]]][source]

Retrieve the live conduit hook map for a specific Spellbook.

Defaults registered under None apply when this Book has no event-specific list. Book-specific lists replace defaults for that event. Returns the sole live map when no merge is needed; otherwise returns a merged dict borrowing the configured lists. Callers must use local runtime hook APIs to change a live conduit without mutating configuration-owned lists.

Shape:

{ hook_name: [callables...] }

Parameters:

spellbook_id (str) -- The ID of the Spellbook whose conduit hooks should be retrieved.

Returns:

Mapping of hook name -> list of callables currently registered for that Spellbook. Returns an empty dict if no conduit hooks exist.

Return type:

Dict[str, list[Callable[..., Any]]]

Raises:

RuntimeError -- If the configuration is cleaned.

get_meld_hooks(spellbook_id: str) → Dict[str, list[Callable[[...], Any]]][source]

Retrieve the live meld hook map for a specific Spellbook.

Defaults registered under None apply when this Book has no event-specific list. Book-specific lists replace defaults for that event. Returns the sole live map when no merge is needed; otherwise returns a merged dict borrowing configured lists. Runtime changes must use Meld's local hooks.

Parameters:

spellbook_id (str) -- The ID of the Spellbook whose meld hooks should be retrieved.

Returns:

Mapping of hook name -> list of callables currently registered for that Spellbook. Returns an empty dict if no meld hooks exist.

Return type:

Dict[str, list[Callable[..., Any]]]

Raises:

RuntimeError -- If the configuration is cleaned.

get_hooks(spellbook_id: str) → Dict[str, list[Callable[[...], Any]]][source]

Retrieve a merged detached hook map for compatibility callers.

Contract:

  • Combines default hooks and this Book's event-specific lists. A Book-specific list replaces the default for the same event.

  • Returns a DETACHED MERGED VIEW of the conduit and meld hook maps - a new dict of new lists. Mutating the result does NOT change registered hooks; use with_hook / with_hooks for that.

  • An id with no registered hooks receives defaults, or an empty map when no defaults were configured.

Threading:

State transitions are applied under the configuration lock.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the configuration has been cleaned.

Returns:

Detached merged view of conduit and meld hooks.

Return type:

Dict[str, list[Callable[..., Any]]]

Parameters:

spellbook_id -- Book whose effective Conduit and Meld events are requested.

with_phase_scheduler_workers(workers: int) → SpellbookConfiguration[source]

Set the number of worker threads used by the Resolution Phase Scheduler. Must be >= 1.

Parameters:

workers (int) -- Number of worker threads.

Returns:

This same configuration instance (for chaining).

Return type:

SpellbookConfiguration

Contract:

  • Validates the worker count before writing it.

  • Mutates the live configuration and returns self.

with_phase_scheduler_barrier_timeout(timeout_milliseconds: int) → SpellbookConfiguration[source]

Set the barrier timeout in milliseconds used by the Resolution Phase Scheduler. Must be >= 0.

Parameters:

timeout_milliseconds (int) -- Barrier timeout in milliseconds.

Returns:

This same configuration instance (for chaining).

Return type:

SpellbookConfiguration

Contract:

  • Rejects zero and excessively large values up front.

  • Mutates the live configuration and returns self.

with_hook(spellbook_id: str | None, hook_name: str, hook: Callable[[...], Any]) → SpellbookConfiguration[source]

Fluent

Register a single system hook for a specific Spellbook and return "self".

This is a fluent wrapper over: meth:add_hook, supporting all valid hook names defined in: attr:_ALLOWED_HOOKS.

Example

(SpellbookConfiguration()
    .with_defaults()
    .with_hook("spellbook-123", "on_meld_pre_resolve", trace_meld_enter)
    .with_hook("spellbook-123", "on_conduit_cleanup_complete", cleanup_fn)
    .finalize())

Contract:

  • REGISTERS ONE HOOK FOR ONE SPELLBOOK ID - the first parameter is the spellbook id, not the hook name. Hooks are per-spellbook, so the same configuration can carry different hooks for different books. An explicit None Book ID supplies defaults for future Books.

  • Fluent wrapper over add_hook; it delegates and returns self, adding no validation of its own. Hook names are validated against the allowed-hook set by the underlying call.

  • ADDITIVE: registering again appends rather than replacing, so calling it twice with the same callable registers it twice.

  • Refused once frozen.

Threading:

State transitions are applied under the configuration lock.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the configuration has been cleaned.

Returns:

This configuration, for fluent chaining.

Return type:

SpellbookConfiguration

Parameters:
  • spellbook_id -- Book ID, or None to register a default event callback.

  • hook_name -- The lifecycle event to attach to.

  • hook -- The callable to invoke. Recorded by NAME only - the crystallizer cannot restore the function itself.

with_hooks(spellbook_id: str | None = None, **hooks: Any) → SpellbookConfiguration[source]

Fluent

Register multiple system hooks for a specific Spellbook in one call and return "self".

Each keyword argument maps a hook name to either:

  • A single callable, or

  • An iterable of callables.

Example

(SpellbookConfiguration()
    .with_defaults()
    .with_hooks("spellbook-123",
        on_meld_pre_resolve=trace_meld_enter,
        on_conduit_pre_created=log_conduit_construction,
        on_contract_created=[observer_1, observer_2],
    )
    .finalize())

Contract:

  • REGISTERS MANY HOOKS FOR ONE SPELLBOOK ID in a single call; the first parameter is the spellbook id and each keyword is a hook name. Omitting the ID registers defaults for future Books.

  • Each keyword value may be a SINGLE CALLABLE or an ITERABLE OF CALLABLES; both shapes are accepted and flattened into the same registration.

  • ADDITIVE, like with_hook - it appends to whatever is already registered rather than replacing it.

  • Refused once frozen.

Threading:

State transitions are applied under the configuration lock.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the configuration has been cleaned.

Returns:

This configuration, for fluent chaining.

Return type:

SpellbookConfiguration

Parameters:
  • spellbook_id -- Book ID, or None for default event callbacks.

  • hooks -- One or more callables to invoke, in registration order.

with_defaults() → SpellbookConfiguration[source]

Fluent

Load Melder’s standard defaults into this configuration and return self so you can keep chaining.

Behaviour:

  • Sets local rich-config defaults: disposal=False, disposal_method_names=[], enforce_priority_disposal_methods=False.

  • Respects idempotency and immutability rules (raises if frozen or cleaned).

Contract:

  • MUTATES THIS OBJECT and returns self; not a copying builder.

  • SEEDS ONLY WHAT IS MISSING. It delegates to load_default_dictionary(), which writes a default ONLY when the key is absent (if key not in self._properties). Values you set earlier are PRESERVED, not replaced.

  • Therefore it is NOT a reset. Calling it after configuring will not restore defaults - it will do nothing to any key you already set. Call it first if you want defaults underneath your overrides, or at any point if you only want the gaps filled.

  • Does NOT clear previously registered hooks - it seeds value defaults, not hook registrations.

  • Refused once frozen.

Threading:

State transitions are applied under the configuration lock.

Lifecycle / Cleanup:

Guarded by check_cleaned().

Raises:

RuntimeError -- If the configuration has been cleaned.

Returns:

This same configuration instance (for chaining).

Return type:

SpellbookConfiguration

with_disposal(enabled: bool = True) → SpellbookConfiguration[source]

Enable or disable disposal features and return self.

Parameters:

enabled -- True to enable disposal semantics; False to disable.

Returns:

This same configuration instance (for chaining).

Return type:

SpellbookConfiguration

Contract:

  • Writes only the disposal flag.

  • Returns self for chaining.

with_enforce_priority_disposal_methods(enabled: bool = True) → SpellbookConfiguration[source]

Store the disposal-method group priority and return this configuration.

Parameters:

enabled -- True places matching book methods before spell-only methods. False (the configuration default) places the book block last; it does not disable the book's contribution. In both modes, shared names occur only in the book block, in configuration order.

Contract:

  • Stages policy only; does not match methods or invoke disposal.

  • Both groups keep their own order; shared names belong to the book.

  • May be changed during assembly; the existing freeze boundary seals it.

  • Bool type checking occurs during validate/finalize, without coercion.

Threading:

Uses set_property's existing configuration mutation lock.

Raises:

RuntimeError -- If this configuration is frozen or cleaned.

Returns:

This same instance for fluent chaining.

Return type:

SpellbookConfiguration

with_disposal_method_names(names: list[str]) → SpellbookConfiguration[source]

Replace the entire list of disposal method names and return self.

Example

cfg.with_disposal_method_names(["close", "cleanup"])
Parameters:

names -- Full replacement list of method names (strings).

Returns:

This same configuration instance (for chaining).

Return type:

SpellbookConfiguration

Contract:

  • Replaces the entire disposal-method list.

  • Type-checks the container before writing it.

add_disposal_methods(*names: str) → SpellbookConfiguration[source]

Append one or more disposal method names (deduplicated, order-preserving) and return self.

Behaviour:

  • Initializes the list to [] if unset.

  • Preserves existing order; adds new names at the end if not already present.

Parameters:

*names -- One or more method names to add.

Returns:

This same configuration instance (for chaining).

Return type:

SpellbookConfiguration

Contract:

  • Initializes the disposal-method list when absent.

  • Preserves order while deduplicating names.

finalize() → SpellbookConfiguration[source]

Validate and freeze, returning self.

Returns:

This same configuration instance (for chaining).

Return type:

SpellbookConfiguration

Contract:

  • Runs the full validation pipeline.

  • Freezes the configuration on success.

  • Returns self for chaining.

build() → SpellbookConfiguration[source]

Fluent alias for finalize().

Contract:

  • Performs the same validation-and-freeze behaviour as finalize().

  • Returns self for chaining.

Returns:

This configuration, validated and frozen. Must be

frozen before a Conduit can be conjured.

Return type:

SpellbookConfiguration

Topic reference Β· Full contents