Cost Model & Offer Selection¶
Overview¶
Provisioning now uses a deterministic cost model so planners can justify every choice. Each offer reports its base cost (operation type) and a proximity modifier derived from the graph structure. Selection sorts by the sum of those two values and breaks ties with the provisioner id, making replay stable and debuggable.
Base Costs¶
Operation |
Base Cost |
Description |
|---|---|---|
|
10 |
Reuse an existing node without modifications. |
|
50 |
Update an existing node in place. |
|
100 |
Clone and evolve an existing node. |
|
200 |
Instantiate a new node from a template. |
These values map to :class:~tangl.vm.provision.offer.ProvisionCost and live on
:class:~tangl.vm.provision.offer.ProvisionOffer.base_cost.
Proximity Modifiers¶
Graph distance influences the final score for EXISTING offers:
Relationship |
Modifier |
Example |
|---|---|---|
Same block |
|
Node already attached to the requesting block. |
Same scene |
|
Node and requester share the same immediate parent subgraph. |
Same episode |
|
Node lives elsewhere in the current episode. |
Elsewhere |
|
Node is outside the current episode hierarchy. |
These values are added on top of the base cost; the sum is stored on offer.cost. Template
provisioning does not apply a proximity modifier because it always creates a new instance.
Selection Algorithm¶
Collect offers from all provisioners and attach metadata (provisioner id, layer, selection criteria).
Deduplicate EXISTING offers per provider so the cheapest proposal survives.
Sort the remaining offers by
(cost, proximity, registration order).Record metadata (reason, all offers, selected provider) for the requirement.
Accept the winning offer and emit :class:
~tangl.vm.provision.offer.BuildReceiptplus aselection_auditentry in :class:~tangl.vm.provision.offer.PlanningReceipt.
Because template creation costs 200 and distant reuse tops out at 10 + 20 = 30, existing entities
will always win unless the requirement policy is explicitly CREATE.
Debugging & Auditing¶
Use :class:tangl.vm.debug.PlanningDebugger to print audit data during development:
from tangl.vm.debug import PlanningDebugger
receipt = frame.records[-1] # last PlanningReceipt emitted by the frame
PlanningDebugger.print_receipt(receipt)
Each entry lists all offers, their costs, proximity descriptions, and which provisioner won.
Developers can also call PlanningDebugger.compare_offers(offers) to compare raw offers before the
planner runs.
Troubleshooting¶
“Why did it reuse a distant node?” – Existing nodes max out at
30, so they beat template creation unless the requirement policy isCREATEor no existing offers qualify.“Why was my template ignored?” – Templates only run when the requirement includes
template_refortemplate. GraphProvisioner skips those requests.“How do I force a fresh instance?” – Set
requirement_policy: CREATEin the script. The cost model still records the offer but GraphProvisioner will decline to compete.“The audit trail is empty.” – Ensure planning handlers returned a :class:
~tangl.vm.provision.offer.PlanningReceipt. Off-main-thread tests may need to call :func:tangl.vm.dispatch.planning.plan_collect_offers/plan_select_and_applyto populate it.