---
stability: stable
---

# Stability policy for `docs/contracts/`

This directory ships the **public contract surface** of `agent-config`.
Every file here declares a stability level in YAML frontmatter:

```yaml
---
stability: stable | beta | experimental
---
```

The level dictates how the contract may be linked from the public
surface (README, AGENTS.md, `docs/architecture.md`) and what kind of
change to it requires what kind of release.

## Levels

### `stable`

- **Breaking change** requires a **SemVer-major** bump (`X.0.0`).
- README, AGENTS.md, and `docs/architecture.md` MAY link to it
  without a marker.
- Typical content: settled ADRs (decisions don't reopen without a
  successor ADR); fully released contracts that have shipped through
  one major release without breaking.

### `beta`

- **Breaking change** is allowed in a **minor-version** release
  (`1.X.0`), provided the change appears in `CHANGELOG.md` under a
  `### Breaking` heading.
- README, AGENTS.md, and `docs/architecture.md` MAY link to it,
  **provided** the link text or the surrounding sentence carries a
  visible `(beta)` marker.
- Typical content: flow contracts and recipes that are shipped and
  load-bearing, but whose surface is expected to evolve before a
  SemVer-major lock.

### `experimental`

- **Breaking change** is allowed in **any release** (including
  patches), with a CHANGELOG note.
- README, AGENTS.md, and `docs/architecture.md` MUST NOT link to
  experimental contracts. Only the index inside `docs/contracts/`
  may reference them.
- Typical content: spike artefacts, runtime modules in pilot status,
  early API drafts not yet wired into a roadmap-locked phase.

## Frontmatter requirement

Every `*.md` under `docs/contracts/` (except this `STABILITY.md` file
itself) MUST start with a YAML frontmatter block declaring `stability:`.

The link checker (`scripts/check_public_links.py`, P0.1b) reads this
frontmatter and:

- **fails CI** when README / AGENTS.md / `docs/architecture.md` links to
  a contract marked `experimental`, to a missing target, or into
  `agents/settings/contexts/` (internal surface).
- **warns** (non-fatal in default mode; fatal under `--strict`) when a
  public-surface link to a `beta` contract has no `(beta)` marker in
  the surrounding text.
- ignores `stable` links.

Run `task check-public-links` locally; `task ci` invokes the same
checker in default mode.

## Promotion path

`experimental → beta → stable`. Demotion is allowed (e.g. `stable →
beta` to permit a refactor) but appears in `CHANGELOG.md` under
`### Breaking` and gets a SemVer-major bump.

Promotion criteria:

- `experimental → beta` — at least one shipped roadmap phase has
  consumed the contract end-to-end without a breaking change.
- `beta → stable` — at least one SemVer-minor release has shipped
  with the contract unchanged, or the contract has been explicitly
  frozen as part of a roadmap step.

## Beta-review markers

Every `stability: beta` contract MUST carry exactly one of the
following frontmatter markers (audit-acceptance for the periodic beta
review; see `road-to-productization.md` § P5.4):

| Marker | Shape | Meaning |
|---|---|---|
| `promote-to: stable` | literal | Contract has been ≥ 30 days in beta, zero breaking changes in the last 14 days, ≥ 1 consumer reference. Schedule promotion in the next release. |
| `keep-beta-until: YYYY-MM-DD` | ISO date | API still moving or consumer count = 0. Date is the next review deadline (max 90 days from the last review). |
| `superseded-by: <contract-id>` | string | Replaced by a stable contract. Slated for deprecation, not deletion. |

The audit is repeated whenever the `keep-beta-until` date passes for
≥ 25 % of beta contracts, or at the start of any roadmap phase that
touches the contract surface.

## Current contracts

See the file headers themselves for current levels. The frontmatter is
the authoritative source — this list is illustrative, not load-bearing,
and is generated by `scripts/check_public_links.py --list`.

## Budget contracts

Numeric caps on the always-active rule surface. These are **load-bearing
constants** — every reply the agent emits pays the always-rule cost, so
the budget is part of the public contract, not an implementation detail.

The accounting model since Phase 0.2 of `road-to-structural-optimization`
is **Model (b) literal** — a rule's effective size is its own char count
plus every context it loads (transitively, depth ≤ 2). See
[`load-context-budget-model.md`](load-context-budget-model.md) (beta)
for the contract and the transitional allowlist.

| Constant | Value | Stability | Owner |
|---|---|---|---|
| Total always-rule budget | **49,000 chars** (extended) | `stable` | `tests/test_always_budget.py::test_always_rules_total_extended_within_tolerance` |
| G3 tolerance band | **2 % overshoot** (≤ 49,980 chars) | `beta` | `scripts/check_always_budget.py` · `tests/test_always_budget.py` |
| Warn threshold (CI) | **80 % (39,200 chars)** | `beta` | `scripts/check_always_budget.py` |
| Fail threshold (CI) | **90 % (44,100 chars)** below 100 %; **> 102 %** above | `beta` | `scripts/check_always_budget.py` |
| Per-rule cap | **6,000 chars** (extended) with transitional allowlist | `beta` | `tests/test_always_budget.py::test_no_unallowlisted_per_rule_breach` · `scripts/check_always_budget.py` |
| Top-3 combined cap | **24,500 chars** (extended, 50 % of total) | `beta` | `tests/test_always_budget.py::test_top3_extended_under_cap` · `scripts/check_always_budget.py` |
| `load_context:` nesting cap | **depth ≤ 2** | `beta` | `tests/test_always_budget.py::test_load_context_depth_within_cap` · `scripts/check_always_budget.py` |

**Promises a consumer can rely on:**

- The total budget will not silently grow. A bump to `TOTAL_CAP`
  requires a `### Breaking` CHANGELOG entry and a SemVer bump per the
  `stable` policy above.
- The CI gate (`task check-always-budget`, run on every PR) prevents
  drift toward the cap without a maintainer noticing — the warn/fail
  thresholds give two PR-cycles of advance notice before the wall.
- Per-rule and top-3 caps prevent a single monster rule re-emerging
  under the global cap. The current per-rule cap is `beta` because the
  numbers are still being tuned against real-world rule churn; promotion
  to `stable` follows one SemVer-minor cycle without a tightening or
  loosening change.

**What's intentionally not promised:**

- The exact composition of the top-5 set. Rules may be split, merged,
  or relocated to `contexts/` between releases as long as the global
  and per-rule caps still hold.
- The condensed-vs-uncondensed delta. The CI gate runs against the
  shipped `dist/agent-src/` (condensed) directory; the uncondensed
  source size is informational only.

## See also

- [`docs/architecture.md`](../architecture.md) — package architecture overview
- [`rule-priority-hierarchy.md`](rule-priority-hierarchy.md) — which always-rule wins on conflict
