# Allocation (/docs/strategies/allocation)



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:

```mermaid
stateDiagram-v2
    [*] --> planned
    planned --> partially_applied
    partially_applied --> applied
    partially_applied --> failed
    partially_applied --> abandoned
    applied --> [*]
    failed --> [*]
    abandoned --> [*]
```

| Strategy                                                    | Output           | Availability       |
| ----------------------------------------------------------- | ---------------- | ------------------ |
| [Allocation plan](#allocation-plan)                         | batch-allocation | Supported contract |
| [Capacity-weighted allocator](#capacity-weighted-allocator) | batch-allocation | Built in           |
| [Portfolio quota allocator](#portfolio-quota-allocator)     | batch-allocation | Built in           |

<Callout title="Allocation vs. per-item selection" type="info">
  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.
</Callout>

## Allocation plan [#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 [#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 [#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](/docs/strategies/scoring#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*.

## Related [#related]

<Cards>
  <Card title="Portfolio balance" href="/docs/strategies/scoring#portfolio-balance">
    The per-decision version of share-cap enforcement.
  </Card>

  <Card title="Ranked slates" href="/docs/strategies/ranked">
    The ordered-output shape allocation plans build on.
  </Card>

  <Card title="Strategy contracts" href="/docs/strategies/contracts#strategy-contract">
    How the allocation\_plan capability is declared and validated.
  </Card>
</Cards>
