# Spell spaces, lineage, and scopes that end Prerequisite: [Beginner scopes](../beginner/scopes.md). Keep the scope boundary explicit: a child conduit, a conduit lineage, and a spell space answer different questions about where instances are shared. | Lifetime | Scope demonstrated by the lessons | | --- | --- | | `unique_per_conduit` | A root and its child resolve different instances | | `unique_per_conduit_lineage` | Root, child, and grandchild share one family instance | | `unique_per_spell_space` | Reuse inside one spell space; separate instances across spaces | ## A job owns its child scope Create a lesser conduit for a job, resolve the job's objects through it, and call the child's `cleanup()` when the job ends. The scoped-cleanup lesson compares the root and job sessions, ends the child, and resolves through the root again. The root has a longer lifetime and is cleaned up separately when the application ends. The runtime retains the objects it manages. Dropping your last local variable does not end that ownership; [cleanup and memory ownership](../beginner/cleanup.md) explains why the explicit end of the scope matters. ## A lesser conduit can grow into a named root A lesser can also have its own discovery name without being promoted. Run [Named lesser conduits](../examples/intermediate/41-named-lesser-conduits.md) for a complete example with prewarming, collisions, per-conduit state and cleanup. ```python job = root.create_lesser_conduit(name="job-42") cloud = root.get_conduit_cloud() try: assert cloud.get_conduit_by_name("job-42") is job job.meld("JobBuffer") finally: job.cleanup() assert not cloud.has_conduit_name("job-42") ``` Supply a name only at creation. Names are exact, nonempty strings, unique among active named roots and lessers in the frame. There is no later lesser rename. Cleanup unregisters and clears the name **before** the scope enters the pool; the next acquisition supplies its own name or stays anonymous. Anonymous scopes do not enter Cloud discovery. This works in automatic and dynamic mode. Use a name when another part of the application needs to find the same live scope. A direct reference is enough when the job stays local. Cloud returns a borrowed reference: it does not extend the owner's lifetime or make reuse after cleanup safe. Names are discovery addresses, not matching-lifetime tags; the registered `Existence` still decides where objects are shared. Aether reaches the same directory from the runtime root: `Aether().get_conduit_by_name("job-42")` returns the named scope in the default frame (pass `aetheric_frame_name` for another frame), and `Aether().get_conduit_by_id(job.id)` finds any live scope by id, named or anonymous. The root-only lookups carry root names, such as `get_root_conduit_by_name`. Frames have lookups of their own that never create anything (0.2.8208): `Aether().find_frame("ops")` returns the live frame or `None`, `Aether().get_frame("ops")` raises `ValueError` when it is missing, and `Aether().list_frame_names()` lists the live frames in the order they were created. The frame-scoped calls above create the default frame when it is missing; these never do, not even for "default". A frame you find is borrowed, so its owner can still clean it. For checks before you hand a configuration over: `frame.shared_spellbook_configuration` is the configuration Spellbooks in that frame adopt when the frame shares one, `job.spellbook` is the Spellbook a conduit resolves through, `SpellbookConfiguration.aether_frame` and `frozen` report a configuration's frame and whether it is settled, and `AethericFrameConfiguration.frozen` does the same for a frame's posture. None of them changes anything. ### Promote only when independent ownership is needed In a dynamic world, `upgrade_to_normal(name=...)` promotes the existing child. The promotion lesson asserts that a previously created per-conduit workbench is the same object afterward, then finds the promoted conduit through cloud lookup. Read [dynamic mode](dynamic-linking.md) before using this operation. Promotion can retain the lesser name or choose another available name. It keeps the conduit ID but creates its own empty Spellbook; naming alone does none of that. [Clusters](../advanced/clusters.md) add a group-wide lifetime. The original cluster lesson remains in its saved Intermediate collection and is linked from that guide. ## Purge creations while keeping the scope Run [Purge unneeded objects](../examples/intermediate/39-purge-unneeded-objects.md) for a complete example. It releases one finished job buffer, keeps its siblings alive, purges the remaining buffers by name, and creates a fresh buffer afterward. It also checks that spell-space purge stays local and final cleanup does not dispose already-purged objects twice. Use `purge` to dispose and remove the retained creations for one registered target without ending its scope or removing its registration: ```python removed = conduit.purge("Job") removed = space.purge("RequestSession") ``` Names, class/function references, spellframe/binding selectors, and explicit `spell_id=` work through the same discovery as `meld`. The return value is the number removed, or `0` when that authorized store has no matching creation. `purge_all=True` is the default: a `many` target removes its whole retained bucket. Only `many` objects with configured disposal are retained; untracked results are not affected by purge. An existing object provides a second input path: purge inspects its class and passes that reference through the existing binding lookup. Choose the shortcut or the explicit selectors for your binding. Both use the same scoped purge: ```python job = conduit.meld("Job") removed = conduit.purge(job, purge_all=False) # Remove this object only. removed = conduit.purge(job) # Remove remaining entries for its binding. ``` Single-object removal requires the instance and returns `0` if that object is not retained in the authorized store. Other instances remain available for later purge or normal scope cleanup. The same options work on `SpellSpace`. | Lifetime | Who may purge it | | --- | --- | | `many` | The conduit or spell space holding those local creations | | `unique_per_spell_space` | That spell space only | | `unique_per_conduit` | That conduit only | | `unique_per_conduit_lineage` | The lineage root conduit | | `unique_per_conduit_cluster` | The elected cluster leader | | `unique` | The spell's owning conduit | | `managed_spell_quantity` | No one: a conduit's purge returns `0`, and a spell space refuses it | A spell space always stays within its own creations. It cannot purge objects belonging to its conduit, lineage root, or cluster. A managed spell's pool is not purged. Give its objects back with `ReleaseManagedSpell().release(obj)`; the pool disposes idle objects when it trims them and when the root that owns it is torn down. [Managed spell pools](managed-spells.md) walks through the whole lifecycle. Purge follows the configured disposal methods and their ordering. It attempts other selected objects after an object's disposal fails, then raises an `ExceptionGroup`; removed entries remain removed. A subsequent meld can create a fresh factory-backed instance using the existing compiled context. References already held by application objects are not rewritten. An externally supplied object remains referenced by its registration, so purging its store entry does not unbind it or turn it into a factory. A `many` dependency with configured disposal belongs to the conduit or spell space that built it, not to the object it was passed to. Purging that object runs only the object's own disposal methods; closing what it holds is up to them. The dependency stays in the store that built it: that scope's end disposes it, and purging the dependency's own binding there removes every instance the store holds, including one a live object still uses. ## The workflow in code These are the core steps from the saved example. Use its complete linked script for all class definitions and setup. ```{literalinclude} ../downloads/02_intermediate/29_scoped_cleanup_lesser_conduits.py :language: python :pyobject: main :linenos: ``` ## Runnable examples - [Scoped cleanup lesser conduits](../examples/intermediate/29-scoped-cleanup-lesser-conduits.md) — Intermediate 29 - [Named lesser conduits](../examples/intermediate/41-named-lesser-conduits.md) — Intermediate 41 - [Spellspace scoped resolution](../examples/intermediate/04-spellspace-scoped-resolution.md) — Intermediate 04 - [Existence unique per conduit lineage](../examples/intermediate/05-existence-unique-per-conduit-lineage.md) — Intermediate 05 - [Lesser conduits child scopes](../examples/intermediate/07-lesser-conduits-child-scopes.md) — Intermediate 07 - [Upgrade to normal](../examples/intermediate/28-upgrade-to-normal.md) — Intermediate 28 - [Purge unneeded objects](../examples/intermediate/39-purge-unneeded-objects.md) — Intermediate 39 [All intermediate examples](../examples/intermediate/index.md) · [Level contents](index.md) · [Full contents](../contents.md) [Canonical page source](https://github.com/Synaptic724/melder/blob/4b364a338debcb1c010d0f6e9dcbbabb62945ba0/docs/intermediate/scopes.md)