# Contracts (/docs/strategies/contracts)



Contracts are the machine-readable governance layer the rest of the routing
surface stands on. A **descriptor** renders the authoring UI; a **contract** tells
headless tools and validators what a strategy can safely consume and produce. With
contracts in place, strategies can be versioned, replayed, experimented on, and
gated before they ever touch live traffic.

They are also the foundation the certified-strategy marketplace stands on: a
strategy someone else authored can only be trusted in your routing once it
resolves to a contract and passes certification against it. Pricing that adoption
now ships as an opt-in surface (`artifact_pricing.strategy.enabled`): a price
pins an exact strategy version, so publishing a new version never inherits an old
price, and the price is resolved at promotion — the gate a tenant passes before
adoption. An unpriced strategy is not a free one.

Still on the roadmap: returning the adopted terms on the wire. The price is
load-bearing at the gate — a resolution failure refuses promotion — but the
promotion response carries no price field, so a client cannot yet read the terms
it accepted.

<Callout title="The one rule everything else enforces" type="warn">
  Every new strategy must ship its **descriptor and contract in the same change**,
  and no strategy name, alias, recipe reference, or MCP/API surface may exist unless
  it **resolves to a contract**. A contract that declares a ranked or allocation
  output but doesn't advertise the matching capability fails `Validate()` — the
  inconsistency is caught at registration, not in production.
</Callout>

## Strategy contract [#strategy-contract]

A **machine-readable, versioned declaration** of everything a strategy needs,
emits, and guarantees — validated for compatibility *before* it runs.

| Field                    | Examples                                                                                 | Meaning                          |
| ------------------------ | ---------------------------------------------------------------------------------------- | -------------------------------- |
| `SourceKind`             | `builtin`, `module`, `remote`, `pipeline`, `recipe`                                      | Where the strategy comes from.   |
| `ObjectiveTags`          | `fairness`, `sla`, `value`, `yield`, `reliability`, `eligibility`                        | What it optimizes for.           |
| `Capabilities`           | `weighted`, `explain`, `allocation_plan`, `ranked_select`                                | Optional features it supports.   |
| `OutputShapes`           | `single_selection`, `ranked_slate`, `multi_winner`, `allocation_plan`, `no_assignment`   | What a decision looks like.      |
| `ExecutionShapes`        | `linear_route`, `dag_strategy_step`, `pipeline_stage`, `batch_route`, `shadow`, `replay` | How and where it runs.           |
| `FactRequirements`       | keyed feature requirements with scope + freshness                                        | The typed facts it reads.        |
| `GovernanceRequirements` | `certification`, `learning_dataset`, `entitlement`, `quota`, `promotion`, `policy_pack`  | What must be in place to run it. |
| `StateModel`             | `stateless`, `process_local`, `distributed`, `learner_backed`, `window_backed`           | Its state scope.                 |
| `Determinism`            | `deterministic`, `seeded_random`, `exploratory_bandit`, `nondeterministic_remote`        | Reproducibility profile.         |
| `FallbackPolicy`         | `fail_closed`, `warn`, `fallback_strategy`, `use_neutral_score`                          | How it degrades.                 |

**Cross-field rules are enforced.** `Validate()` rejects contradictions — an
allocation output without the `allocation_plan` capability, a ranked output
without `ranked_select`, a replayable *remote* strategy that doesn't record remote
evidence, or an &#x2A;*exploratory bandit that lacks a learning-dataset governance
requirement.** That last rule is why [Thompson sampling](/docs/strategies/learning#thompson-sampling)
and LinUCB must declare a dataset before they can be certified.

## Strategy recipe [#strategy-recipe]

A **versioned authoring template** for a business objective. A recipe generates
route or pipeline **starter payloads** and **verification checklists** — but it
**never executes a strategy**. It is the packaging layer between "I want fair
distribution" and a concrete, contract-backed route.

Built-in recipes cover the common objectives:

| Recipe                        | Objective                                 |
| ----------------------------- | ----------------------------------------- |
| `fair_distribution`           | Smooth fair distribution.                 |
| `sla_rescue`                  | Deadline / SLA rescue.                    |
| `value_optimized_leads`       | Outcome-value / yield optimization.       |
| `portfolio_balance`           | Portfolio share balancing.                |
| `availability_aware`          | Calendar-aware routing.                   |
| `quota_cost_guarded`          | Connector quota and cost guardrails.      |
| `brownout_failover`           | Brownout / reliability failover.          |
| `eligibility_preferred_match` | Recipient eligibility preferred matching. |
| `ranked_slate_fallback`       | Ranked alternates before assignment.      |
| `batch_margin_allocation`     | Batch assignment under constraints.       |
| `custom_strategy_canary`      | Remote / custom strategy rollout.         |

<Callout title="missing_dependencies" type="warn">
  A recipe reports `missing_dependencies` when the module that owns its target
  strategy isn't installed. For example, `value_optimized_leads` can reference
  `predicted_value`, which belongs to the contrib
  [`predicted` module](/docs/strategies/learning); a tenant remote strategy requires
  a promoted deployment. Install or enable the owner before applying the generated
  starter.
</Callout>

## API and MCP surface [#api-and-mcp-surface]

Contracts and recipes are read-only governance objects exposed on the Routing API
and mirrored to MCP clients:

| Routing API              | MCP tool                             |
| ------------------------ | ------------------------------------ |
| `ListStrategyContracts`  | `routing.strategy.contracts`         |
| `GetStrategyContract`    | `routing.strategy.describe_contract` |
| `ValidateStrategyPlan`   | `routing.strategy.validate_plan`     |
| `ListStrategyRecipes`    | `routing.strategy.recipes`           |
| `GenerateStrategyRecipe` | `routing.strategy.generate_recipe`   |

`ValidateStrategyPlan` checks a planned strategy or pipeline against the surface
it will run on, without executing strategy code. Validate for the surface you
intend:

* `linear_route` — normal route requests and pool defaults.
* `dag_strategy_step` — workflow DAG `routing.strategy` steps.
* `pipeline_stage` — a child strategy inside a [strategy pipeline](/docs/strategies/pipelines).
* `batch_route` — [allocation-plan](/docs/strategies/allocation) consumers.
* `dry_run`, `shadow`, `replay` — non-production simulation modes.

A strategy can have valid params and still fail plan validation if its output
shape, execution shape, replay mode, required facts, or governance requirements
are incompatible with the surface.

## Strategy feature snapshot [#strategy-feature-snapshot]

A **typed, read-only fact contract** loaded once per decision — after eligibility
filtering, before selection — and handed to the strategy as
`SelectRequest.Features`. Strategies read the snapshot; they **never** query
outcome, quality, or connector repositories directly. That single discipline is
what makes decisions replayable, shadowable, and fair to compare in experiments.

The loader projects candidate facts under canonical numeric keys, including:

```text
conversion_rate            avg_outcome_value       total_outcomes
quality_score              quality_confidence      return_rate
response_time              sla_compliance          compliance_score
current_load_ratio         available_capacity      attribute_match_score
margin                     cost                    portfolio_share
availability.*             reliability.*           eligibility.*
```

Rules the snapshot enforces:

* known bounded values are validated as finite numbers; confidence values must be
  in `[0,1]`;
* **missing data is an absent key**, not a zero — a strategy can tell "no data"
  from "genuinely zero";
* decision metadata records only compact status keys (`strategy_features_status`,
  `strategy_features_version`, `strategy_features_candidates`) — never the full
  feature map.

If no loader is configured, selection is unchanged. A partial loader error warns
and continues by default, preserving local capacity and deadline facts; a strategy
can opt into fail-closed loading with `strategy_options.require_features=true`.
Invalid snapshots always fail closed.

## Strategy state plane [#strategy-state-plane]

Feature snapshots are decision *inputs*. The **state plane** is the routing-owned,
mutable state that changes *because* a decision was accepted — SWRR current
weights, fair-catchup assignment windows, bandit posteriors, cooldowns,
exploration budgets, scheduler cursors. It lives in
`application/routing/strategystate/`, separate from both feature snapshots and
outcome-learning evidence.

**Read a snapshot, emit an intent.** During `Select` a strategy may read only the
frozen `StrategyStateReadSet` on `SelectRequest.State`. It does **not** write
durable state. Instead it returns deterministic `StateMutationIntents` on the
response; the routing finalizer applies them **only after the decision is durably
accepted**, then records receipts linked to the decision. Retries reuse the
idempotency key so a duplicate finalizer attempt returns the same receipt rather
than re-applying the transition.

```mermaid
sequenceDiagram
  participant S as Strategy.Select
  participant St as State plane
  participant F as Routing finalizer
  St->>S: frozen StrategyStateReadSet
  S-->>F: StateMutationIntents on response
  Note over F: decision durably accepted
  F->>St: apply intents
  St-->>F: receipt linked to decision
```

State is addressed by a strict key. A valid scope carries `tenant_id`, a route
identity (`routing_bundle_id` or `route_version_id`), `pool_id`, `strategy_name`,
and a version (`strategy_version` or `contract_version`); the full key adds a
`kind` and `entity_key`. Built-in kinds include `swrr_weights`,
`assignment_window`, `fairness_debt`, `cooldown`, `exploration_budget`,
`bandit_model_state_ref`, and `scheduler_cursor`. The plane exposes
Inspector / Ledger / Mutator / Reader / Snapshotter / Store ports.

<Callout title="Why state lives outside Select" type="warn">
  Mutating state inside `Select` would make the strategy unreplayable and unsafe to
  shadow-evaluate — a shadow run would corrupt live state. Read-snapshot / intent /
  receipt keeps selection pure, so the same decision can be replayed, shadowed, and
  audited without side effects. Postgres is the authoritative audit/replay store;
  Redis may be a fast path behind the shared adapter but never the sole authority.
</Callout>

## Related [#related]

<Cards>
  <Card title="Governance" href="/docs/strategies/governance">
    Certification and shadow campaigns built on these contracts.
  </Card>

  <Card title="Scoring strategies" href="/docs/strategies/scoring">
    The main consumers of the feature snapshot.
  </Card>

  <Card title="Pipelines" href="/docs/strategies/pipelines">
    Each child stage is validated against the pipeline\_stage execution shape.
  </Card>

  <Card title="Write a custom strategy" href="/docs/strategies/writing-a-custom-strategy">
    How to declare a contract and certify a strategy of your own.
  </Card>
</Cards>
