Routing Strategies

Allocation

Batch-level assignment — durable allocation plans, capacity-weighted greedy planning, and portfolio quota enforcement optimized across a whole batch.

Allocation strategies decide assignments for a whole batch at once, against a global objective and hard/soft constraints — not one item at a time. Where a single-winner strategy greedily picks the best recipient for this item, an allocator reasons about the batch: it can spread a scarce recipient's capacity across items, honor per-recipient caps globally, and leave items unassigned when constraints require.

Allocation strategies return an AllocationPlan that records assignments, unassigned items, scores, and constraint outcomes. Ductor includes capacity-weighted and portfolio-quota allocators for this batch-oriented path. Each plan carries an execution status:

planned partially_applied applied failed abandoned
StrategyOutputAvailability
Allocation planbatch-allocationSupported contract
Capacity-weighted allocatorbatch-allocationBuilt in
Portfolio quota allocatorbatch-allocationBuilt in

Allocation vs. per-item selection

Per-item selection is greedy: item 1 takes the best recipient, and item 2 sees whatever's left. Batch allocation optimizes the set — it can deliberately give item 1 a second-best recipient so item 2, which has no other option, still gets served. Reach for allocation when items compete for the same scarce capacity.

Allocation plan

Output: batch-allocation

A durable batch contract that optimizes a whole batch against a global objective and hard/soft constraints — not per-item greedy selection. It records per-item rank, score, and reason, lists unassigned items explicitly, reports a global objective value, and is deterministic for identical inputs.

Why it matters. It is the authoritative shape all allocators produce. Because it is durable and deterministic, an allocation plan can be persisted, replayed, and audited: you can prove why item 7 went unassigned and what the batch-level objective value was.

How it's shaped. A strategy implements AllocationStrategy.Allocate, which takes an AllocationRequest (a batch of selection requests) and returns an AllocationPlan whose assignments are addressed by AllocationAssignment.RequestIndex. Hard-constraint violations must be explicit in the plan or surfaced as an error — an allocator never silently breaks a hard cap. The capability is advertised as allocation_plan, and a contract that declares an allocation output must carry that capability.

Where it fits. A batch execution shape (batch_route) rather than the single-item Select path.

Capacity-weighted allocator

Output: batch-allocation · Built in

A greedy batch optimizer that deducts capacity as it assigns. It orders items by priority and deadline, scores candidates by weight plus remaining capacity, and — critically — deducts the planned units as it goes, so later items see true remaining capacity rather than the batch's starting capacity.

Why use it. A practical allocator for fulfillment and dispatch. Naive per-item routing over-assigns to a high-weight recipient because each decision sees full capacity; this allocator remembers what it has already planned, so it stops assigning to a recipient once its capacity is spent and moves to the next best.

How it works. Items are processed priority- and deadline-first; each assignment reduces the chosen recipient's remaining capacity for subsequent items.

Where it fits. Batch allocation. It is the greedy, capacity-aware default; for share/concentration limits use the quota allocator below.

Portfolio quota allocator

Output: batch-allocation · Built in

Enforces per-recipient and per-group caps across an entire batch. Hard caps are never violated; soft caps are penalized; items are left unassigned when the constraints require it. It is concentration control at batch scale — the batch counterpart of the per-decision portfolio balance scorer.

Why use it. When a batch must respect global limits — no recipient gets more than X items, no group (region, vendor, team) gets more than Y share — and you'd rather leave an item unassigned than blow a hard cap. Compliance-driven distribution and fair-share fulfillment are the typical cases.

How it works. It tracks per-recipient and per-group counts/shares as it allocates, refusing assignments that would breach a hard cap and penalizing those that breach a soft cap. Unfulfillable items are returned as explicitly unassigned. It implements AllocationStrategy and is registered by builtin.RegisterBuiltins.

Where it fits. Batch allocation, when constraints are about distribution limits rather than capacity.