Custom World Runtime Hooks

StoryTangl story no longer exposes the old fabula.materialize phase bus. World-specific behavior now plugs in through explicit world and runtime hooks, which keeps materialization passes deterministic and makes extension points easier to see in code review.

Current Extension Points

Hook

Implement on

Used by

Purpose

get_authorities()

world/domain facet

StoryGraph.get_authorities() -> PhaseCtx.get_authorities()

Add runtime behavior registries for tasks like gather_ns, prereqs, postreqs, or scoped media/template lookups.

get_template_scope_groups(caller=..., graph=...)

world/templates facet

script manager, resolver, materializer

Add extra template registries in nearest-to-broadest scope order.

get_media_inventories(caller=..., requirement=..., graph=...)

world/resources facet

runtime media resolution

Expose world-scoped media inventories without hard-coding global registries.

frame.local_behaviors / ledger.local_behaviors

runtime or tests

PhaseCtx local authorities

Attach opt-in local behavior registries for experiments, auditing, or one-off tests.

Registering World Runtime Behaviors

World authorities are just explicit :class:~tangl.core.behavior.BehaviorRegistry instances returned by a facet’s get_authorities() method.

from dataclasses import dataclass

from tangl.core import BehaviorRegistry, DispatchLayer
from tangl.story import World


world_registry = BehaviorRegistry(
    label="weather.world",
    default_dispatch_layer=DispatchLayer.APPLICATION,
)


@world_registry.register(task="gather_ns")
def inject_weather(caller, *, ctx, **_):
    return {"weather": "fog"}


@dataclass(slots=True)
class WeatherDomain:
    def get_authorities(self):
        return [world_registry]


world = World.from_script_data(script_data=script, domain=WeatherDomain())
result = world.create_story("weather_demo")

Extending Template Scope

Additional authored templates should be contributed through get_template_scope_groups(...) rather than through a separate materialize phase.

from dataclasses import dataclass

from tangl.core import TemplateRegistry
from tangl.story import World


@dataclass(slots=True)
class ExtraTemplates:
    extra: TemplateRegistry

    def get_template_scope_groups(self, *, caller=None, graph=None):
        return [list(self.extra.values())]


world = World.from_script_data(
    script_data=script,
    templates=ExtraTemplates(extra=bonus_templates),
)

Best Practices

  1. Return explicit registries or groups from world facets; avoid mutating global dispatch registries in tests unless the behavior is truly shared.

  2. Keep world authorities focused on runtime behavior. Use template/media scope hooks for lookup data instead of squeezing that data through dispatch tasks.

  3. Use frame.local_behaviors or ledger.local_behaviors for short-lived experiments and test overrides rather than adding new global handlers.

  4. Treat the old materialize-task docs and MaterializationContext examples as retired. The current story materializer is organized as explicit passes, not a public per-phase dispatch bus.