tangl.core dispatch

Behavior and dispatch primitives that higher layers use to register and execute hooks.

Related design docs

Related notes

Behaviors

class Behavior(registry=None, graph=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, func=<function Behavior.<lambda>>, task=None, priority=Priority.NORMAL, dispatch_layer=DispatchLayer.LOCAL, wants_caller_kind=None, wants_exact_kind=True)[source]

Example

>>> b = Behavior(func=lambda *nums, **kwargs: sum(nums))
>>> receipt = b(1, 2, 3)
>>> f"sum{receipt.args}={receipt.result}"
'sum(1, 2, 3)=6'
>>> deferred = b.defer()
>>> assert deferred.result is None
>>> deferred.resolve(4, 5, 6)
15
>>> f"sum{deferred.args}={deferred.result}"
'sum(4, 5, 6)=15'
>>> c = Behavior(func=lambda *_, **__: True, wants_caller_kind=Entity)
>>> assert Selector(caller_kind=Entity).matches(c) and not Selector(caller_kind=dict).matches(c)
class BehaviorRegistry(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, members=<factory>, default_task=None, default_priority=Priority.NORMAL, default_dispatch_layer=DispatchLayer.APPLICATION)[source]

Example

>>> br = BehaviorRegistry()
>>> f = br.register(lambda *nums, **kwargs: sum(nums), task="sum")
>>> g = br.register(lambda *args, **kwargs: ''.join([str(a) for a in args]),
...                 task="join", priority=Priority.EARLY)
>>> next( br.execute_all(task="sum", call_args=(1, 2, 3)) ).result
6
>>> next( br.execute_all(task="join", call_args=('a', 'b', 'c')) ).result
'abc'
>>> CallReceipt.gather_results( *br.execute_all(call_args=(1, 2, 3)) )
...     # join triggers first even tho registered last, b/c lower priority
['123', 6]
class CallReceipt(_ctx=None, *, uid=<factory>, label=None, tags=<factory>, templ_hash=None, seq=None, origin_id=None, result=None, callback=None, args=None, kwargs=None, ctx=None, **kwargs_)[source]

Aggregation Modes:

Receipt aggregation or folding summarizes dispatch traces into a concrete result or list of results. One key detail is that behaviors that return a None result are tracked with a receipt for audit, but do not participate in result reduction. Several generic aggregators are implemented as class functions on Receipt (handling for collections of receipts). Additional aggregators can be introduced at other layers.

Supported aggregation helpers: - first_result: first non-None result (single winner). - last_result: last non-None result (override pattern). - all_true: all non-None results are truthy (validation gates). - gather_results: collect all non-None results. - merge_results: flatten lists or merge dicts (later entries override).

Examples

>>> receipts = [ CallReceipt(result=None),
...              CallReceipt(result=1),
...              CallReceipt(result=0),
...              CallReceipt(result=None) ]
>>> CallReceipt.gather_results(*receipts)
[1, 0]
>>> CallReceipt.first_result(*receipts)
1
>>> CallReceipt.last_result(*receipts)
0
>>> CallReceipt.all_true(*receipts)
False
>>> CallReceipt.merge_results(CallReceipt(result=[1,2,3]),
...                           CallReceipt(result=[4,5,6]))  # flattens
[1, 2, 3, 4, 5, 6]
>>> dict( CallReceipt.merge_results(CallReceipt(result={'a': 'foo'}),
...                                 CallReceipt(result={'b': 'bar'}),
...                                 CallReceipt(result={'a': 'baz'})) )  # late overrides
{'a': 'baz', 'b': 'bar'}

Dispatch module

Default global dispatch registry and explicit hook pairs for core lifecycle events.

Hook pairs are exposed as explicit decorators and runners:

  • creation: - on_create / do_create (pre-structuring, data -> data) - on_init / do_init (post-init, caller -> None)

  • registry indexing: - on_add_item / do_add_item (registry, item -> item) - on_get_item / do_get_item (registry, item -> item) - on_remove_item / do_remove_item (registry, item -> None)

  • graph relationships: - on_link / do_link (caller, node -> None) - on_unlink / do_unlink (caller, node -> None)

Context contract

Dispatch chaining is driven by the runtime context protocol used by tangl.core.behavior.BehaviorRegistry:

  • ctx.get_authorities() contributes extra registries by layer.

  • ctx.get_inline_behaviors() contributes one-off callables.

Callers normally pass _ctx to hook-aware APIs (for example Entity(..., _ctx=ctx), Entity.structure(..., _ctx=ctx), Registry.add(..., _ctx=ctx)), or rely on ambient context via using_ctx.

Aggregation contracts

  • do_create folds hook results with CallReceipt.merge_results().

  • do_add_item and do_get_item use CallReceipt.last_result().

  • do_init, do_remove_item, do_link, and do_unlink force receipt evaluation with CallReceipt.gather_results().

Example

>>> assert Entity(label="bar").label == "bar"
>>> _ = on_init(func=lambda *, caller, ctx = None, **kwargs: setattr(caller, "label", "foo"))
>>> item = Entity(
...     label="bar",
...     _ctx=SimpleNamespace(
...         get_authorities=lambda: [],
...         get_inline_behaviors=lambda: [],
...     ),
... )  # calls global dispatch by default
>>> assert item.label == "foo"
>>> q = BehaviorRegistry()
>>> _ = q.register(task="init", dispatch_layer=DispatchLayer.APPLICATION,
...                func=lambda *, caller, ctx = None, **kwargs: setattr(caller, "label", "baz"))
>>> item = Entity(
...     label="bar",
...     _ctx=SimpleNamespace(
...         get_authorities=lambda: [q],
...         get_inline_behaviors=lambda: [],
...     ),
... )
>>> assert item.label == "baz"
>>> dispatch.clear()  # always clean up global registries after using

Dispatch Layer Mapping

  • GLOBAL / core / core.dispatch / auditing and logging

  • SYSTEM / vm / vm.dispatch / phase handlers and provisioning

  • SYSTEM / service / service.dispatch / API and persistence

  • APPLICATION / story / story.dispatch / content rendering and domain rules

  • APPLICATION / mechanics / story.dispatch / story semantics extensions

  • APPLICATION / discourse / story.dispatch / prose model extensions

  • APPLICATION / media / story.dispatch / media extensions

  • AUTHOR / world / world.dispatch / world-specific mechanics

  • LOCAL / vm.frame / vm.frame.dispatch / one-off handlers

Creation hooks

on_create(func, **kwargs)[source]

Register a create hook on the global dispatch registry.

do_create(*, data, ctx)[source]

Run create hooks and merge returned mapping updates into structuring input.

on_init(func, **kwargs)[source]

Register an init hook on the global dispatch registry.

do_init(*, caller, ctx)[source]

Run init hooks for a newly constructed caller.

Registry hooks

on_add_item(func, **kwargs)[source]

Register an add_item hook for registry insertion.

do_add_item(registry, item, ctx)[source]

Run add_item hooks and return the final inserted entity.

The last non-None receipt result wins. If no hook returns a replacement, the original item is returned.

on_get_item(func, **kwargs)[source]

Register a get_item hook for registry lookup interception.

do_get_item(registry, item, ctx)[source]

Run get_item hooks and return the final fetched entity.

on_remove_item(func, **kwargs)[source]

Register a remove_item hook for post-removal inspection.

do_remove_item(registry, item, ctx)[source]

Run remove_item hooks after registry removal.

Graph hooks

Register a link hook for graph relationships.

Run link hooks for edge endpoint or group membership linking.

Register an unlink hook for graph relationship teardown.

Run unlink hooks after edge endpoint or group membership unlinking.