> ## Documentation Index
> Fetch the complete documentation index at: https://developers.kardinal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Hard vs soft constraints

> Feasibility logic and why a problem can be declared infeasible.

Every field in a Kardinal plan falls into one of two categories: constraints the engine **must** respect, and preferences it respects **if it can**. Knowing which is which explains why a stop sometimes goes unplanned even though "the API didn't return an error."

## Hard constraints

A hard constraint rules out any solution that violates it. If the engine cannot find a way to serve a stop without breaking one, that stop is left unserved rather than the constraint being bent.

Typical hard constraints in the Kardinal model:

* **`authorizedTimeWindows`** — the engine never plans a stop outside these windows. If none of a stop's authorized windows can be reached, the stop is unserviceable. This is why the documentation recommends making authorized windows as wide as realistically possible: they define the outer bound of what's even considered a valid visit, not a target.
* **`requiredSkills`** — a resource must have *every* skill an order requires, or it cannot be assigned to it (for example, a tailgate-truck requirement).
* **Capacities** — a stop's `capacities` must fit within what a resource can carry (and what it has left after prior stops); there is no partial match.
* **Order structure** — `successiveStops` (stops of an order must be visited consecutively), `maxStopSpan` (maximum time between a pickup and its delivery), and `removalStrategy: "lifo"` (a resource can only unload the last thing it loaded) are all structural constraints the engine always respects strictly.
* **`workingTimeWindow` / `maxWorkingDuration` / `maxDistanceInKm`** on a resource — bounds on when and how much a resource can work, which the engine never exceeds.

### A contractual window is not automatically a hard one

It's tempting to model a client-facing delivery window as `authorizedTimeWindows` because it's contractual — but "contractual" and "hard" answer two different questions. The question that decides which one to use isn't "is this window written into an agreement?" — it's "what should happen if the fleet can't hit it?"

* If arriving late is undesirable but not disqualifying — a school delivery running 20 minutes late is still worth making — model it as **`preferredTimeWindows`**. The engine tries to hit it and counts any late arrival as `delay`, but it won't drop the stop just because it can't make the window exactly. A preferred window only tolerates lateness, never earliness: the engine never plans a stop before the start of its preferred window. If arriving before the stated opening hour is acceptable, a preferred window won't give you that — widen the window instead, moving its `begin` back to the earliest time a visit is genuinely acceptable.
* Only use **`authorizedTimeWindows`** when a visit outside the window is truly unserviceable — a site that's physically closed outside those hours, a security checkpoint that won't admit a vehicle early, a customer who refuses the delivery.

**A stated opening/closing hour is necessary, but not sufficient, evidence for "hard."** A data column that gives a site's opening and closing hours only tells you the site *has* hours — it doesn't by itself tell you what happens if a vehicle arrives outside them. That's a separate fact: does the site actually turn a late-arriving vehicle away (or refuse to let it start early), or does it simply prefer being served within those hours while still accepting a late visit? The first case is genuinely `authorizedTimeWindows`; the second is `preferredTimeWindows` built from the exact same hours. Don't infer which one applies from the mere presence of an opening-hours column, and don't treat "the site has hours" and "the site enforces those hours as a hard cutoff" as the same claim — they aren't. If nothing in the data or the client context states the actual dispatch tolerance (an explicit penalty for a late arrival, a fact like "closes and locks the gate," a confirmed refusal policy), that tolerance is exactly the kind of open question this page already asks you to check ("what should happen if the fleet can't hit it?") — treat it as unresolved and default to `preferredTimeWindows` rather than assuming hard.

Getting this wrong in the hard direction has a silent, expensive failure mode: every stop whose contractual window can't be reached exactly is dropped and reported as an `unaffectedStopIds` entry instead of being delivered late. If most or all of your delivery windows come from a contract or SLA, default to `preferredTimeWindows` and confirm with the business which windows, if any, are truly non-negotiable — don't assume "contractual" implies "hard." See [Handling infeasibility](/guides/handling-infeasibility) for how to recognize this failure pattern once it happens, or the [requirements briefing checklist](/guides/requirements-briefing#6-delivery-stops) for the question to ask up front so this doesn't need to be inferred later.

## Soft constraints

A soft constraint has a cost, not a wall. The engine tries to satisfy it, but accepts a violation if that's what it takes to do better on a higher-ranked objective.

* **`preferredTimeWindows`** — if a stop can't be reached inside its preferred window, it isn't dropped: the gap between the planned time and the window is counted as **delay**, which the `minimizeDelay` objective then tries to reduce. The engine automatically intersects authorized and preferred windows, so a preferred window is only ever a tighter target inside an authorized one, never a way to widen it.
* **`priority`** on resources and orders — priority is a soft, graduated dial rather than a binary include/exclude flag. Lower numbers matter more (priority `0` outranks priority `3`), and priority levels are strict tiers: the engine gives up any number of lower-priority orders to plan one more of a higher priority, and leaves the lowest-priority resources unused first (see [Objectives split by priority](/concepts/objectives#objectives-split-by-priority)). Nothing is hard-coded as "always excluded." Marking an order `"optional": true` isn't just a coarser version of the same idea: it also moves the order to a different objective. Mandatory stops count towards `maximizeMandatoryStops` — however low their `priority` — and optional stops only towards `maximizeOptionalStops`, so an optional stop is only planned if that objective is in your list (it is by default, but a custom list can leave it out). In the default order the first comes before `minimizeResources` while the second comes after it. So the engine can add a vehicle to plan a mandatory stop, but never just to plan an optional one.
* **`preferredStopTags`** on a resource — the soft counterpart to `requiredSkills`. Stops whose tags match are preferably given to that resource, scored through the `maximizePreferredStops` objective (in the default list), but another resource can still serve them if that does better on a higher-ranked objective.
* **`overlappingCapacitiesByStopTag`** (plan level) — a target maximum number of resources simultaneously present at stops sharing a tag, such as a depot's loading docks. Overruns are minimized by `minimizeOverOverlappingCapacitiesOnStops`, according to its position in `objectives`.
* **`avoidEarlyLoadingsByResourceTag`** (plan level) or **`avoidEarlyLoadings`** (per resource) — declares stop tags, and optionally capacities, at which early loading should be avoided. These declarations only take effect through the `minimizeEarlyLoadings` objective, which isn't in the default list: add it to your objectives explicitly, or they have no effect.

`lateDeparture` isn't a soft constraint: it isn't traded off against any objective, it only decides how each resource's departure is timed. With `lateDeparture: false` (the default), resources leave as early as their `workingTimeWindow` allows, which can create idle waiting time before a later stop window. With `lateDeparture: true`, the engine shifts a resource's departure as late as possible while still arriving at each stop as early as possible.

## How the engine arbitrates conflicts

Two mechanisms do the arbitration, and both come from [how the objectives are ordered](/concepts/objectives):

1. **Lexicographic objectives** decide what "better" means when trade-offs are unavoidable — for instance, whether protecting mandatory stops matters more than minimizing delay, or the reverse, depending on how you've ordered your `objectives` list.
2. **Priority elimination** decides *who* gets sacrificed first when something has to give — the lowest-priority resources or orders absorb the impact before higher-priority ones are touched.

## Why a stop, not always the problem, becomes infeasible

Kardinal's model rarely declares an entire plan infeasible as a single event. Instead, the engine returns a solution that is valid for everything it *could* place, and reports the rest explicitly:

* **`unaffectedStopIds`** — stops that could not be planned within the hard constraints (no authorized time window reachable, no resource with matching skills/capacity/availability, etc.).
* **`unusedResourceIds`** — resources that ended up with nothing assigned to them, generally because `minimizeResources` outranks using them, or because none of the remaining orders match their constraints.
* **`tours[].isValid`** — a per-resource flag reflecting whether that specific tour respects all hard constraints.

In other words, "infeasible" in Kardinal is usually a property of a specific stop or resource, not a rejection of the whole request — the API still returns `200` with a solution, just one where some stops or resources are left out. Diagnosing *which* hard constraint caused a given stop to end up in `unaffectedStopIds` is covered in the [Handling infeasibility](/guides/handling-infeasibility) how-to guide.

## See also

* [Handling infeasibility](/guides/handling-infeasibility) — diagnosing which hard constraint left a specific stop unplanned.
* [Requirements briefing](/guides/requirements-briefing) — the questions to ask up front so hard-vs-soft doesn't need to be inferred later.
* [Modeling advanced constraints](/guides/advanced-constraints) — worked examples for the fields named throughout this page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.